Разверните даты начала и окончания в последовательность дат начала и окончания по календарному месяцу - PullRequest
0 голосов
/ 27 ноября 2018

Учитывая таблицу

id   start          end
1   22/03/2016    05/06/2016
2   17/08/2016    29/08/2016
3   22/09/2017    25/12/2017

Я пытаюсь разбить на календарный месяц как следующую таблицу

id   start         end
1   22/03/2016    31/03/2016
1   01/04/2016    30/04/2016
1   01/05/2016    05/06/2016
2   17/08/2016    29/08/2016
3   22/09/2017    30/09/2017
3   01/10/2017    31/10/2017
3   01/11/2017    30/11/2017
3   01/12/2017    25/12/2017

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

#sample data
df <- data.frame("starting_date" = as.Date(c("2016-03-22", "2016-08-17", "2017-09-12")),
             "end_date" = as.Date(c("2016-06-05", "2016-08-29", "2017-12-25")),
             col3=c('1','2', '3'))

df1 <- df[,1:2] %>% 
rowwise() %>%
do(rbind(data.frame(matrix(as.character(c(
.$starting_date, 

seq(.$starting_date, .$end_date, by=1)[grep("\\d{4}-\\d{2}-31|\\d{4}-\\d{2}-01", seq(.$starting_date, .$end_date, by=1))],

.$end_date)), ncol=2, byrow=T))
  )
) %>%
data.frame() %>%
`colnames<-`(c("starting_date", "end_date")) %>%
mutate(starting_date= as.Date(starting_date, format= "%Y-%m-%d"),
     end_date= as.Date(end_date, format= "%Y-%m-%d"))

#add temporary columns to the original and expanded date column dataframes
df$row_idx <- seq(1:nrow(df))
df$temp_col <- (year(df$end_date) - year(df$starting_date)) +1
df1 <- cbind(df1,row_idx = rep(df$row_idx,df$temp_col))

#join both dataframes to get the final result
final_df <- left_join(df1,df[,3:(ncol(df)-1)],by="row_idx") %>%
  select(-row_idx) 
final_df

Если кто-нибудь знает, как изменить код или лучший способ сделать это, я буду очень признателен.

Ответы [ 2 ]

0 голосов
/ 27 ноября 2018

Мы предполагаем, что в примере выборки в вопросе есть ошибка, поскольку третья строка охватывает части двух месяцев, поэтому ее следует разбить на две строки.

Определите Seq, для которого были заданы переменные start и end Date, в столбце data.frame start и end, а затем запустите его на каждом id, используя group_by:

library(dplyr)
library(zoo)

Seq <- function(start, end) {
  ym <- seq(as.yearmon(start), as.yearmon(end), 1/12)
  starts <- pmax(start, as.Date(ym, frac = 0))
  ends <- pmin(end, as.Date(ym, frac = 1))
  unique(data.frame(start = starts, end = ends))
}

fmt <- "%d/%m/%Y"
DF %>%
  mutate(start = as.Date(start, fmt), end = as.Date(end, fmt)) %>%
  group_by(id) %>%
  do(Seq(.$start, .$end)) %>%
  ungroup

подача:

# A tibble: 9 x 3
     id start      end       
  <int> <date>     <date>    
1     1 2016-03-22 2016-03-31
2     1 2016-04-01 2016-04-30
3     1 2016-05-01 2016-05-31
4     1 2016-06-01 2016-06-05
5     2 2016-08-17 2016-08-29
6     3 2017-09-22 2017-09-30
7     3 2017-10-01 2017-10-31
8     3 2017-11-01 2017-11-30
9     3 2017-12-01 2017-12-25

Примечание

Ввод DF в воспроизводимой форме:

Lines <- "
id   start          end
1   22/03/2016    05/06/2016
2   17/08/2016    29/08/2016
3   22/09/2017    25/12/2017"
DF <- read.table(text = Lines, header = TRUE)
0 голосов
/ 27 ноября 2018

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

SETUP

library(tidyverse)
library(lubridate)

df <- data.frame(
  id = c('1', '2', '3'),
  starting_date = as.Date(c("2016-03-22", "2016-08-17", "2017-09-12")),
  end_date = as.Date(c("2016-06-05", "2016-08-29", "2017-12-25")),
  stringsAsFactors = FALSE
)

df
#>   id starting_date   end_date
#> 1  1    2016-03-22 2016-06-05
#> 2  2    2016-08-17 2016-08-29
#> 3  3    2017-09-12 2017-12-25

РЕШЕНИЕ

df %>%
  group_by(id) %>%
  mutate(
    date_seq = list(seq.Date(starting_date, end_date, by = "month") %>% ceiling_date("month") - 1)
  ) %>%
  unnest() %>%
  mutate(row = row_number()) %>%
  mutate(
    new_end_date = if_else(row == max(row), end_date, date_seq),
    new_start_date = if_else(row == min(row), starting_date, floor_date(new_end_date, "month"))
  ) %>% 
  select(
    id, new_start_date, new_end_date
  )
#> # A tibble: 8 x 3
#> # Groups:   id [3]
#>   id    new_start_date new_end_date
#>   <chr> <date>         <date>      
#> 1 1     2016-03-22     2016-03-31  
#> 2 1     2016-04-01     2016-04-30  
#> 3 1     2016-06-01     2016-06-05  
#> 4 2     2016-08-17     2016-08-29  
#> 5 3     2017-09-12     2017-09-30  
#> 6 3     2017-10-01     2017-10-31  
#> 7 3     2017-11-01     2017-11-30  
#> 8 3     2017-12-01     2017-12-25

ОБЪЯСНЕНИЕ

Многое из того, что здесь происходит, занимаетместо в первом mutate вызове, который создает date_seq.Чтобы понять это, рассмотрим следующее:

seq.Date(ymd("2016-03-22"), ymd("2016-06-05"), by = "month")
# [1] "2016-03-22" "2016-04-22" "2016-05-22"

seq.Date(ymd("2016-03-22"), ymd("2016-06-05"), by = "month") %>% 
  ceiling_date("month")
# [1] "2016-04-01" "2016-05-01" "2016-06-01"

seq.Date(ymd("2016-03-22"), ymd("2016-06-05"), by = "month") %>% 
  ceiling_date("month") - 1
# [1] "2016-03-31" "2016-04-30" "2016-05-31"

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

df %>%
  group_by(id) %>%
  mutate(
    date_seq = list(seq.Date(starting_date, end_date, by = "month") %>% ceiling_date("month") - 1)
  ) %>%
  unnest()

Оттуда я надеюсь, что все относительно просто.row_number, вероятно, можно было бы заменить на что-нибудь более причудливое, например, first/last, но я подумал, что за этим проще следовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...