Заполнить все пробелы между начальной и конечной датами с помощью dplyr

#r #dplyr

#r #dplyr

Вопрос:

Допустим, у меня есть data.frame, который выглядит следующим образом:

 user_df = read.table(text = "person_id job_number job_type start_date end_date
                  1 1 B 2012-11-01 2014-01-01
                  1 2 A 2016-02-01 2016-10-01
                  1 3 A 2016-12-01 2020-01-01
                  1 4 B 2020-01-01 2021-01-01
                  2 1 A 2011-03-01 2012-08-01
                  2 2 B 2013-01-01 2020-01-01
                  2 3 A 2020-01-01 2021-01-01
                  2 4 B 2021-01-01 2021-01-17
                  3 1 A 2005-03-01 2011-03-01
                  3 2 B 2012-01-01 2014-01-01", header = T)
 

У каждого person_id из них есть дата начала и окончания для данного задания. Я хотел бы вставить строки между пустым пространством между заданиями и создать дополнительный вызываемый столбец unemployed , для которого установлено значение 1 для этих столбцов.

Результирующий data.frame для первых нескольких строк будет выглядеть следующим образом:

 user_df = read.table(text = "person_id job_number job_type start_date end_date unemployed
                  1 1 B 2012-11-01 2014-01-01 0
                  1 1 B 2014-01-01 2016-02-01 1
                  1 2 A 2016-02-01 2016-10-01 0
                  1 2 A 2016-10-01 2016-12-01 1
                  1 3 A 2016-12-01 2020-01-01 0
                  1 4 B 2020-01-01 2021-01-01 0
                  2 1 A 2011-03-01 2012-08-01 0
                  2 1 A 2012-08-01 2013-01-01 1
                  2 2 B 2013-01-01 2020-01-01 0", header = T)
 

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

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

Ответ №1:

 library(dplyr)
user_df %>%
  arrange(start_date) %>%
  group_by(person_id) %>%
  mutate(nextstart = lead(start_date)) %>%
  filter(end_date < nextstart) %>%
  mutate(start_date = end_date, end_date = nextstart, unemployed = 1L) %>%
  select(-nextstart) %>%
  bind_rows(mutate(user_df, unemployed = 0L)) %>%
  arrange(person_id, start_date) %>%
  ungroup()
# # A tibble: 14 x 6
#    person_id job_number job_type start_date end_date   unemployed
#        <int>      <int> <chr>    <chr>      <chr>           <int>
#  1         1          1 B        2012-11-01 2014-01-01          0
#  2         1          1 B        2014-01-01 2016-02-01          1
#  3         1          2 A        2016-02-01 2016-10-01          0
#  4         1          2 A        2016-10-01 2016-12-01          1
#  5         1          3 A        2016-12-01 2020-01-01          0
#  6         1          4 B        2020-01-01 2021-01-01          0
#  7         2          1 A        2011-03-01 2012-08-01          0
#  8         2          1 A        2012-08-01 2013-01-01          1
#  9         2          2 B        2013-01-01 2020-01-01          0
# 10         2          3 A        2020-01-01 2021-01-01          0
# 11         2          4 B        2021-01-01 2021-01-17          0
# 12         3          1 A        2005-03-01 2011-03-01          0
# 13         3          1 A        2011-03-01 2012-01-01          1
# 14         3          2 B        2012-01-01 2014-01-01          0
 

Технически это сравнение по алфавитному сортировке дат; в этом случае его эффект тот же (формат подходит для этого), хотя он будет немного менее эффективным (целочисленная / числовая сортировка быстрее, чем алфавитная сортировка).

Это работает, сначала создавая, а затем фиксируя только незанятые периоды времени,

 user_df %>%
  arrange(start_date) %>%
  group_by(person_id) %>%
  mutate(nextstart = lead(start_date)) %>%
  filter(end_date < nextstart)
# # A tibble: 4 x 6
# # Groups:   person_id [3]
#   person_id job_number job_type start_date end_date   nextstart 
#       <int>      <int> <chr>    <chr>      <chr>      <chr>     
# 1         3          1 A        2005-03-01 2011-03-01 2012-01-01
# 2         2          1 A        2011-03-01 2012-08-01 2013-01-01
# 3         1          1 B        2012-11-01 2014-01-01 2016-02-01
# 4         1          2 A        2016-02-01 2016-10-01 2016-12-01
 

затем переместите переменные, затем добавьте unemployed и, наконец, верните его в исходный набор данных. В этом случае я добавил unemployed к исходному mid- bind_rows ; где это сделать, в основном предпочтение.

Комментарии:

1. Вау, это фантастически умно! большое спасибо. Многому научился, прочитав этот код.

2. надеюсь, быстрый последующий вопрос. Я заметил, что в моих данных у некоторых участников есть даты в одной строке, которые находятся между начальной и конечной датами другой строки. Если, например, они выполняют две работы одновременно, одну между 2005-03-01 — 2011-03-01, а другую между 2006-03-01 и 2007-03-01. Есть ли у вас какие-либо предположения относительно того, как обращаться с этими случаями?

3. Это вопрос с подвохом: для меня это совершенно достоверные данные. Похоже, что это не соответствует вашей последующей обработке. Например, вы предполагаете, что никакие две строки (для одного человека) не могут перекрываться вообще ? Если это так, как вы хотите обрабатывать конфликты? Самая короткая работа? Самая длинная работа? Объединение двух заданий (минимальное начало и максимальное завершение)? Это полностью зависит от контекста, и вам нужно полностью определить. И нет, нет инструмента, который сделает все это за вас, поскольку это ваши ограничения. (Но да, это можно сделать с помощью четко определенных правил.)

4. Хм, извините — я должен был быть более конкретным. Я собираюсь создать новый вопрос, и я свяжу его здесь, если вам интересно ответить.