2016-04-07 3 views
5

Учитывая начальную дату, я хочу сгенерировать последовательность дат с ежемесячными интервалами, гарантируя, что каждый элемент имеет тот же день, что и первоначальная дата или последний день месяца, если тот же день приведет к недействительной дате.Последовательность месячных дат, удостоверяющая, что это тот же день или последний день месяца в случае недействительности

Звучит довольно стандартно, правда?

Использование difftime невозможно. Вот что говорит файл справки из difftime:

Единицы, такие как «месяцев» не представляется возможным, поскольку они не имеют постоянной длины. Чтобы создать интервалы месяцев, кварталов или лет, используйте seq.Date или seq.POSIXt.

Но глядя на файле справки seq.POSIXt Я считаю, что:

Использование «месяц» первые успехи в месяц, не меняя день: если это приводит к недопустимого день месяца, он пересчитывается в в следующий месяц: см. примеры.

Это пример в файле справки.

seq(ISOdate(2000,1,31), by = "month", length.out = 4) 
> seq(ISOdate(2000,1,31), by = "month", length.out = 4) 
[1] "2000-01-31 12:00:00 GMT" "2000-03-02 12:00:00 GMT" 
"2000-03-31 12:00:00 GMT" "2000-05-01 12:00:00 GMT" 

Таким образом, учитывая, что начальная дата на 31 день, это даст некорректные даты в феврале, апреле и т.д. Таким образом, последовательность в конечном итоге на самом деле пропуск этих месяцев, потому что он «рассчитывает вперед» и в конечном итоге с марта-02, а не с 29 февраля.

Если я начну на 2000-01-31, я хотел бы последовательность следующим образом:

  • 2000-01-31
  • 2000-02-
  • 2000-03- 31
  • 2000-04-30
  • ...

И он с hould правильно обрабатывать високосных лет, так что, если начальная дата 2015-01-31 последовательность должна быть:

  • 2015-01-31
  • 2015-02-
  • 2015-03 -31
  • 2015-04-30
  • ...

это только примеры, иллюстрирующие проблему, и я не знаю начальную дату заранее, нет Я могу предположить что-нибудь об этом. Первоначальная дата может быть в середине месяца (2015-01-15), и в этом случае seq отлично работает. Но это также может быть, как и в примерах, ближе к концу месяца по датам, поскольку использование только seq было бы проблематичным (дни 29, 30 и 31). Я не могу предположить, что начальная дата - последний день месяца.

Я осмотрелся, пытаясь найти решение. В некоторых вопросах здесь, в SO (например, здесь) есть «трюк», чтобы получить последний день месяца, получив первый день следующего месяца и просто вычтите 1. И найти первый день «легко», потому что он только день 1.

так что мое решение до сих пор:

# Given an initial date for my sequence 
initial_date <- as.Date("2015-01-31") 

# Find the first day of the month 
library(magrittr) # to use pipes and make the code more readable 
firs_day_of_month <- initial_date %>% 
    format("%Y-%m") %>% 
    paste0("-01") %>% 
    as.Date() 

# Generate a sequence from initial date, using seq 
# This is the sequence that will have incorrect values in months that would 
# have invalid dates 
given_dat_seq <- seq(initial_date, by = "month", length.out = 4) 

# And then generate an auxiliary sequence for the last day of the month 
# I do this generating a sequence that starts the first day of the 
# same month as initial date and it goes one month further 
# (lenght 5 instead of 4) and substract 1 to all the elements 
last_day_seq <- seq(firs_day_of_month, by = "month", length.out = 5)-1 

# And finally, for each pair of elements, I take the min date of both 
pmin(given_dat_seq, last_day_seq[2:5]) 

он работает, но, в то же время, своего рода немой, Hacky и запутанные. Поэтому мне это не нравится. И самое главное, я не могу поверить, что в этом нет более простого способа сделать это.

Может ли кто-нибудь указать мне на более простое решение? (Думаю, это должно быть так просто, как seq(initial_date, "month", 4), но, видимо, это не так). Я искал его и смотрел здесь в списках рассылки SO и R, но помимо трюков, о которых я говорил выше, я не мог найти решение.

+0

не удалось вы просто делаете 'seq (as.Date (" 2015-01-31 ") + 1, length = 4, by =" month ") - 1'? – mtoto

+0

Нет, потому что начальная дата произвольная (в этом конкретном примере я использовал январь-31, чтобы проиллюстрировать проблему, но я не знаю начальную дату заранее, и это не всегда последний день месяца). Если я сделаю так, как вы говорите, и исходная дата будет «2015-01-15», моя последовательность будет всегда на 16-й день, когда она должна быть на 15-й день каждый месяц. Спасибо, ты заставил меня понять, что не было ясно в моем довольно длинном вопросе. Я отредактирую его. – elikesprogramming

ответ

3

Наиболее простым решением является %m+% от lubridate, что решает эту проблему. Итак:

seq_monthly <- function(from,length.out) { 
    return(from %m+% months(c(0:(length.out-1)))) 
} 

Выход:

> seq_monthly(as.Date("2015-01-31"),length.out=4) 
[1] "2015-01-31" "2015-02-28" "2015-03-31" "2015-04-30" 
3

Аналогично lubridate ответ, вот один, используя RcppBDT (который облегает Повысьте Date.Time библиотеку из C++)

R> dt <- new(bdtDt, 2010, 1, 31); for (i in 1:5) { dt$addMonths(i); print(dt) } 
[1] "2010-02-28" 
[1] "2010-04-30" 
[1] "2010-07-31" 
[1] "2010-11-30" 
[1] "2011-04-30" 
R> dt <- new(bdtDt, 2000, 1, 31); for (i in 1:5) { dt$addMonths(i); print(dt) } 
[1] "2000-02-29" 
[1] "2000-04-30" 
[1] "2000-07-31" 
[1] "2000-11-30" 
[1] "2001-04-30" 
R> 
Смежные вопросы