识别数据框中其他日期范围包含的用户日期范围

Identify date ranges within user that are contained by other date ranges in a dataframe

假设我有以下数据:

user_df = read.table(text = "start_date_proper end_date_proper id
1995-01-01 1997-12-31 1
1999-04-01 2000-06-30 1
2006-05-01 2009-08-31 1
2010-01-01 2011-12-31 1
2011-01-01 2011-12-31 1
2000-07-01 2020-12-31 1
2003-01-01 2020-12-31 1", header = T)

多亏了这个 ,我能够确定一行的结束日期和下一行的开始日期之间是否存在间隙,并插入一个新行来填补该间隙。像这样:

user_df = user_df %>%
    arrange(start_date_proper) %>%
    group_by(id) %>%
    mutate(nextstart = lead(start_date_proper)) %>%
    filter(end_date_proper < nextstart) %>%
    mutate(start_date_proper = end_date_proper, end_date_proper = nextstart, unemployed = 1L) %>%
    select(-nextstart) %>%
    bind_rows(mutate(user_df, unemployed = 0L)) %>%
    arrange(id, start_date_proper) %>%
    ungroup()

> user_df
# A tibble: 10 x 4
   start_date_proper end_date_proper    id unemployed
   <chr>             <chr>           <int>      <int>
 1 1995-01-01        1997-12-31          1          0
 2 1997-12-31        1999-04-01          1          1
 3 1999-04-01        2000-06-30          1          0
 4 2000-06-30        2000-07-01          1          1
 5 2000-07-01        2020-12-31          1          0
 6 2003-01-01        2020-12-31          1          0
 7 2006-05-01        2009-08-31          1          0
 8 2009-08-31        2010-01-01          1          1
 9 2010-01-01        2011-12-31          1          0
10 2011-01-01        2011-12-31          1          0

但是,请注意,标识为失业 2009-08-31 -2010-01-01) 的新行实际上并不准确,因为用户在其他行中的 2003-01-01 - 2020-12-31 工作。

所以问题是,是否有一种简单的方法来识别这些情况并适当地设置 unemployed?我的第一个想法是遍历每个 unemployed == 1 的日期,然后遍历用户中的所有其他日期,看看这些日期是否包含 `unemployed ==1 行跨越的日期。但这需要进行相当多的比较,这对我的大数据集来说具有挑战性。有什么想法吗?

将过滤器行替换为

 filter(end_date_proper < nextstart - 1) %>%

因为您只想在下一个片段的开始时间比当前片段的结束时间早一天以上时添加间隙片段。

# A tibble: 9 x 4
  start_date_proper end_date_proper    id unemployed
  <date>            <date>          <int>      <int>
1 1995-01-01        1997-12-31          1          0
2 1997-12-31        1999-04-01          1          0
3 1999-04-01        2000-06-30          1          0
4 2000-07-01        2020-12-31          1          0
5 2003-01-01        2020-12-31          1          0
6 2006-05-01        2009-08-31          1          0
7 2009-08-31        2010-01-01          1          0
8 2010-01-01        2011-12-31          1          0
9 2011-01-01        2011-12-31          1          0

我将在数据中的就业期间内存在失业期间的前提下工作。为此,我将更改样本数据,使失业期落在其他时期之一。 (不重要,但对演示和我的理解很有帮助。)

user_df$start_date_proper[4] <- "2004-06-30"
user_df$end_date_proper[4] <- "2004-07-01"

为了让这个技巧起作用,我们需要 order。也就是说,我们需要假设一行的start_date_proper(如果失业)必须不早于前一行的end_date_proper;同样,失业 end_date_proper 必须不晚于下一行的 start_date_proper。为此,我将使用矢量化 min/max 函数:pminpmax:

现在的工作:

user_df %>%
  arrange(start_date_proper) %>%
  mutate(
    start_date_proper = if_else(
      unemployed > 0,
      pmax(start_date_proper, lag(end_date_proper), na.rm = TRUE),
      start_date_proper),
    end_date_proper = if_else(
      unemployed > 0,
      pmin(end_date_proper, lead(start_date_proper), na.rm = TRUE),
      end_date_proper)
  )
# # A tibble: 10 x 4
#    start_date_proper end_date_proper    id unemployed
#    <chr>             <chr>           <int>      <int>
#  1 1995-01-01        1997-12-31          1          0
#  2 1997-12-31        1999-04-01          1          0
#  3 1999-04-01        2000-06-30          1          0
#  4 2000-07-01        2020-12-31          1          0
#  5 2003-01-01        2020-12-31          1          0
#  6 2020-12-31        2004-07-01          1          1
#  7 2006-05-01        2009-08-31          1          0
#  8 2009-08-31        2010-01-01          1          0
#  9 2010-01-01        2011-12-31          1          0
# 10 2011-01-01        2011-12-31          1          0

现在请注意,失业日期已更改为 (1) 恰好在前一结束之后,以及 (2) 恰好在下一行开始之前。这显然是一条不可能的线,所以应该加一个过滤器:

  filter(end_date_proper >= start_date_proper)

所以完整代码(实际上删除了那个失业期)是:

user_df %>%
  arrange(start_date_proper) %>%
  mutate(
    start_date_proper = if_else(
      unemployed > 0,
      pmax(start_date_proper, lag(end_date_proper), na.rm = TRUE),
      start_date_proper),
    end_date_proper = if_else(
      unemployed > 0,
      pmin(end_date_proper, lead(start_date_proper), na.rm = TRUE),
      end_date_proper)
  ) %>%
  filter(end_date_proper >= start_date_proper)

在不完全重叠的情况下(其中只有部分的失业期不正确),这将压缩失业期,使其不再与之前和以下行。 (这是像这样更复杂的方法的基本原理;@JonSpring 的答案更简单、更易读,但不处理重叠。)

一个失业区从另一个时期的中间开始,但在它之后继续,失业区仍然存在,认为调整了起点。 (这确实是使用像这个答案这样更复杂的东西的唯一原因。@JonSpring 的答案要简单得多,但不允许重新分配日期。)