as.Date дает неожиданный результат в последовательности недельных дат

Я работаю над преобразованием дат на основе недель в даты на основе месяцев.

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

Кто-нибудь может объяснить, что здесь происходит?


person KoenV    schedule 18.01.2017    source источник
comment
Проблема в том, что %u начинается с понедельника, а %U начинается с воскресенья, поэтому as.Date("2016-50-7", format = "%Y-%U-%u") интерпретируется как первый день (воскресенье) недели 50. См. ?strptime для проверки.   -  person lmo    schedule 18.01.2017
comment
после комментария @Imo as.Date("2016-50-7", format = "%Y-%V-%u"), похоже, работает, но в течение нескольких лет будет давать результаты, отличные от вашего примера - насколько это важно, может зависеть от вашего приложения   -  person Miff    schedule 18.01.2017
comment
@Miff на моем компьютере, что приводит к "2016-01-18", что не является правильной датой   -  person Jaap    schedule 18.01.2017
comment
@Miff на моем компьютере я получаю тот же результат, который Яап упоминает 2016-01-18, для всех строк ввода, то есть: 2016-50-4 и так далее.   -  person KoenV    schedule 18.01.2017
comment
@lmo, я бы сказал, что as.Date("2016-50-7", format = "%Y-%U-%u") интерпретируется как седьмой день (воскресенье) 50-й недели.   -  person d.b    schedule 18.01.2017
comment
@DarshanBaral Внимательно посмотрите на вывод OP или скопируйте и вставьте каждую строку кода, и вы поймете, что я имею в виду.   -  person lmo    schedule 18.01.2017


Ответы (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.

Итак, чтобы преобразовать строки даты, предоставленные OP, с использованием ISOweek2date(), необходимо вставить W после первого дефиса, что достигается заменой первого - на -W в каждой строке.

Также обратите внимание, что недели ISO начинаются с понедельника, а дни недели нумеруются от 1 до 7. Год, относящийся к неделе ISO, может отличаться от календарного года. Это видно из приведенных выше примеров дат, где недельная дата 2016-W52-7 преобразуется в 2017-01-01.

О пакете ISOweek

Еще в 2011 году спецификации форматов %G, %g, %u и %V не были доступны для strptime() в версии R для Windows. Это раздражало, поскольку мне приходилось готовить еженедельные отчеты, включая недельные сравнения. Я потратил часы, чтобы найти решение для работы с неделями ISO, рабочими днями ISO и годами ISO. Наконец, я создал пакет ISOweek и опубликовал его в CRAN. Сегодня пакет по-прежнему имеет свои достоинства, поскольку вышеупомянутые форматы игнорируются при вводе (подробнее см. ?strptime).

person Uwe    schedule 18.01.2017
comment
Спасибо за ваш вклад и время. Я буду работать с Даршаном Барансом над вашим постом. - person KoenV; 19.01.2017
comment
Я провел ручное тестирование вашего предложения: например, одну строку кода с использованием пакета ISOweek. Пока это работает отлично. Теперь я запущу это мой полный набор данных и отчитаюсь. - person KoenV; 19.01.2017
comment
При всей своей простоте это продолжает работать без проблем для моего набора данных с датами 2014-2017 (сейчас). Большое спасибо ! - person KoenV; 19.01.2017
comment
Я протестировал решение Уве Блока на наборе данных с датами с 2004 года до половины января 2017 года. Этот подход работает отлично. - person KoenV; 01.02.2017
comment
@KoenV Рад это слышать и благодарю за все испытания. - person Uwe; 01.02.2017

Как сказал @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"
person Jaap    schedule 18.01.2017
comment
Спасибо. Кажется, это работает нормально, за одним исключением, когда количество дней в неделе равно 1, например 2016-50-1, что приводит к NA. - person KoenV; 18.01.2017
comment
@KoenV исправлено, я думаю; добавил пример с 1 в демо. - person Frank; 18.01.2017
comment
@Откровенный. Еще раз спасибо. Кажется, это работает отлично. Позже я проведу дополнительное тестирование на больших наборах данных и сообщу об этом на этом форуме. - person KoenV; 18.01.2017
comment
@Jaap @Frank Правильной строкой формата будет "%G-%V-%u". %G - год, основанный на неделе. Как уже упоминалось, это будет работать только для вывода, поскольку эти спецификаторы формата Принимаются, но игнорируются при вводе. - person Uwe; 19.01.2017
comment
@Frank Ваше предложение работает для большинства дат, но не для некоторых ближе к концу года. Даты, для которых он не работает, следующие (не исчерпывающие): "2014-52-6" "2015-53-6" "2015-53-4" "2015-53-1" "2015-53-2" Это генерирует NA. - person KoenV; 19.01.2017

Проблема в том, что для %u 1 – это Monday, а 7 – Sunday недели. Проблема еще более усложняется тем фактом, что %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".

Лучшим способом сделать это может быть использование подхода, предложенного в ответе Уве Блока. Поскольку вы довольны преобразованием "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"

Я не совсем понимаю, что делать, если год заканчивается в воскресенье.

person d.b    schedule 18.01.2017
comment
Спасибо. Я тоже проверю ваш подход. Чтобы проверить годы, заканчивающиеся на воскресенье, мне нужно включить 2006. Мне нужно время, чтобы получить эти необработанные данные, прежде чем я смогу запустить такой тест. В настоящее время я делаю первый EDA с 2014-2017 годами. - person KoenV; 19.01.2017
comment
@KoenV @Darshan Строка правильного формата будет "%G-%V-%u". %G - год, основанный на неделе. Как уже упоминалось, это будет работать только для вывода, поскольку эти спецификаторы формата Принимаются, но игнорируются при вводе. - person Uwe; 19.01.2017