сделать месячные диапазоны в R - PullRequest
1 голос
/ 23 февраля 2020

У меня есть эта функция для генерации месячных диапазонов, она должна учитывать годы, когда февраль имеет 28 или 29 дней:

   starts     ends
1  2017-01-01 2017-01-31
2  2017-02-01 2017-02-28
3  2017-03-01 2017-03-31

Работает с:

make_date_ranges(as.Date("2017-01-01"), Sys.Date())

Но выдает ошибку с:

make_date_ranges(as.Date("2017-01-01"), as.Date("2019-12-31"))

Почему?

make_date_ranges(as.Date("2017-01-01"), as.Date("2019-12-31"))
Error in data.frame(starts, ends) : 
  arguments imply differing number of rows: 38, 36

add_months <-  function(date, n){
  seq(date, by = paste (n, "months"), length = 2)[2]
}

make_date_ranges <- function(start, end){

  starts <- seq(from = start,
                to =  Sys.Date()-1 ,
                by = "1 month")

  ends <- c((seq(from = add_months(start, 1),
                 to = end,
                 by = "1 month" ))-1,
            (Sys.Date()-1))

  data.frame(starts,ends)

}

## useage
make_date_ranges(as.Date("2017-01-01"), as.Date("2019-12-31"))

Ответы [ 2 ]

1 голос
/ 23 февраля 2020

1) Сначала определите начало месяца, som и конец месяца, функции eom, которые принимают объект класса Date, строку даты в стандартном формате Date или yearmon возьмите и создайте объект класса Date, дающий начало или конец года / месяца.

Используя их, создайте ежемесячный Date ряд s, используя начало каждого месяца месяца / год от from до to. Используйте pmax, чтобы убедиться, что ряд не продолжается до from и pmin, чтобы он не продолжался после to.

Входные аргументы могут быть строками в стандартном формате Date, Date объекты класса или yearmon объекты класса. В случае yearmon предполагается, что пользователь хотел получить полный месяц за каждый месяц. (Оператор if может быть опущен, если вам не требуется поддержка yearmon входов.)

library(zoo)

som <- function(x) as.Date(as.yearmon(x))
eom <- function(x) as.Date(as.yearmon(x), frac = 1)

date_ranges2 <- function(from, to) {
  if (inherits(to, "yearmon")) to <- eom(to)
  s <- seq(som(from), eom(to), "month")
  data.frame(from = pmax(as.Date(from), s), to = pmin(as.Date(to), eom(s)))
}

date_ranges2("2000-01-10", "2000-06-20")
##         from         to
## 1 2000-01-10 2000-01-31
## 2 2000-02-01 2000-02-29
## 3 2000-03-01 2000-03-31
## 4 2000-04-01 2000-04-30
## 5 2000-05-01 2000-05-31
## 6 2000-06-01 2000-06-20

date_ranges2(as.yearmon("2000-01"), as.yearmon("2000-06"))
##         from         to
## 1 2000-01-01 2000-01-31
## 2 2000-02-01 2000-02-29
## 3 2000-03-01 2000-03-31
## 4 2000-04-01 2000-04-30
## 5 2000-05-01 2000-05-31
## 6 2000-06-01 2000-06-30

2) Эта альтернатива использует тот же подход, но определяет начало Месяц (som) и конец месяца (eom) работают без использования yearmon, поэтому требуется только основание R. Он принимает символьные строки в стандартном формате Date или входные данные класса Date и выдает тот же результат, что и (1).

som <- function(x) as.Date(cut(as.Date(x), "month")) # start of month
eom <- function(x) som(som(x) + 32) - 1 # end of month

date_ranges3 <- function(from, to) {
  s <- seq(som(from), as.Date(to), "month")
  data.frame(from = pmax(as.Date(from), s), to = pmin(as.Date(to), eom(s)))
}

date_ranges3("2000-01-10", "2000-06-20")
##         from         to
## 1 2000-01-10 2000-01-31
## 2 2000-02-01 2000-02-29
## 3 2000-03-01 2000-03-31
## 4 2000-04-01 2000-04-30
## 5 2000-05-01 2000-05-31
## 6 2000-06-01 2000-06-20

date_ranges3(som("2000-01-10"), eom("2000-06-20"))
##         from         to
## 1 2000-01-01 2000-01-31
## 2 2000-02-01 2000-02-29
## 3 2000-03-01 2000-03-31
## 4 2000-04-01 2000-04-30
## 5 2000-05-01 2000-05-31
## 6 2000-06-01 2000-06-30
1 голос
/ 23 февраля 2020

Вам не нужно использовать seq дважды - вы можете вычесть 1 день из первых чисел каждого месяца, чтобы получить результаты, и сгенерировать слишком много starts, затем сдвиг и подмножество:

make_date_ranges = function(start, end) {
  # format(end, "%Y-%m-01") essentially truncates end to 
  #   the first day of end's month; 32 days later is guaranteed to be
  #   in the subsequent month
  starts = seq(from = start, to = as.Date(format(end, '%Y-%m-01')) + 32, by = 'month')
  data.frame(starts = head(starts, -1L), ends = tail(starts - 1, -1L))
}
x = make_date_ranges(as.Date("2017-01-01"), as.Date("2019-12-31"))
rbind(head(x), tail(x))
#        starts       ends
# 1  2017-01-01 2017-01-31
# 2  2017-02-01 2017-02-28
# 3  2017-03-01 2017-03-31
# 4  2017-04-01 2017-04-30
# 5  2017-05-01 2017-05-31
# 6  2017-06-01 2017-06-30
# 31 2019-07-01 2019-07-31
# 32 2019-08-01 2019-08-31
# 33 2019-09-01 2019-09-30
# 34 2019-10-01 2019-10-31
# 35 2019-11-01 2019-11-30
# 36 2019-12-01 2019-12-31
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...