A quickish go with my own preferred tools. Is this what you're describing?
library(data.table)
library(fjoin) # install.packages("fjoin", repos=c("https://trobx.r-universe.dev"))
# individual-specific end dates
df_limits <- fread("
id end_date
1 2021-06-15
2 2018-09-03
3 2016-03-30
")
# spells of care
df_spells <- fread("
id spell start end
1 1 2015-01-13 2015-07-19
1 2 2016-04-14 2017-02-07
1 3 2018-05-24 2019-10-15
2 1 2015-07-23 2017-01-05
2 2 2018-02-13 2018-06-04
2 3 2019-07-31 2021-02-04
3 1 2015-02-16 2016-11-19
3 2 2018-04-29 2020-03-01
")
START <- as.IDate("2014-01-01")
# how many months back do we need to go per id?
df_limits[, months_back := 12L * (year(end_date) - year(START)) + month(end_date)]
# expand to a grid for each id
grid <- df_limits[, .(id, end_date, month_end=seq(end_date, length.out = months_back, by = "-1 month")), by=.I][, I := NULL]
grid[, month_start:= fifelse(id==shift(id, type="lead"), shift(month_end, type="lead") +1L, START)]
grid[.N, month_start := START]
setcolorder(grid, c("id", "end_date", "month_start", "month_end"))
setkeyv(grid, c("id", "month_start", "month_end"))
# overlaps of care spells with the grid for each patient
ans <- fjoin_left(grid,
df_spells,
on = c("id", "month_end >= start", "month_start <= end"),
mult.x = "first", # in case there are overlapping care spells (we don't want multiple "hits")
indicate = TRUE) # 3L if match, 1L if not
ans[, in_care := .join==3L]
# output (for patient 2)
ans[id==2L]
Key: <id, month_start, month_end>
.join id end_date month_start month_end spell start end in_care
<int> <int> <IDat> <IDat> <IDat> <int> <IDat> <IDat> <lgcl>
1: 1 2 2018-09-03 2014-01-01 2014-01-03 NA <NA> <NA> FALSE
2: 1 2 2018-09-03 2014-01-04 2014-02-03 NA <NA> <NA> FALSE
3: 1 2 2018-09-03 2014-02-04 2014-03-03 NA <NA> <NA> FALSE
4: 1 2 2018-09-03 2014-03-04 2014-04-03 NA <NA> <NA> FALSE
5: 1 2 2018-09-03 2014-04-04 2014-05-03 NA <NA> <NA> FALSE
6: 1 2 2018-09-03 2014-05-04 2014-06-03 NA <NA> <NA> FALSE
7: 1 2 2018-09-03 2014-06-04 2014-07-03 NA <NA> <NA> FALSE
8: 1 2 2018-09-03 2014-07-04 2014-08-03 NA <NA> <NA> FALSE
9: 1 2 2018-09-03 2014-08-04 2014-09-03 NA <NA> <NA> FALSE
10: 1 2 2018-09-03 2014-09-04 2014-10-03 NA <NA> <NA> FALSE
11: 1 2 2018-09-03 2014-10-04 2014-11-03 NA <NA> <NA> FALSE
12: 1 2 2018-09-03 2014-11-04 2014-12-03 NA <NA> <NA> FALSE
13: 1 2 2018-09-03 2014-12-04 2015-01-03 NA <NA> <NA> FALSE
14: 1 2 2018-09-03 2015-01-04 2015-02-03 NA <NA> <NA> FALSE
15: 1 2 2018-09-03 2015-02-04 2015-03-03 NA <NA> <NA> FALSE
16: 1 2 2018-09-03 2015-03-04 2015-04-03 NA <NA> <NA> FALSE
17: 1 2 2018-09-03 2015-04-04 2015-05-03 NA <NA> <NA> FALSE
18: 1 2 2018-09-03 2015-05-04 2015-06-03 NA <NA> <NA> FALSE
19: 1 2 2018-09-03 2015-06-04 2015-07-03 NA <NA> <NA> FALSE
20: 3 2 2018-09-03 2015-07-04 2015-08-03 1 2015-07-23 2017-01-05 TRUE
21: 3 2 2018-09-03 2015-08-04 2015-09-03 1 2015-07-23 2017-01-05 TRUE
22: 3 2 2018-09-03 2015-09-04 2015-10-03 1 2015-07-23 2017-01-05 TRUE
23: 3 2 2018-09-03 2015-10-04 2015-11-03 1 2015-07-23 2017-01-05 TRUE
24: 3 2 2018-09-03 2015-11-04 2015-12-03 1 2015-07-23 2017-01-05 TRUE
25: 3 2 2018-09-03 2015-12-04 2016-01-03 1 2015-07-23 2017-01-05 TRUE
26: 3 2 2018-09-03 2016-01-04 2016-02-03 1 2015-07-23 2017-01-05 TRUE
27: 3 2 2018-09-03 2016-02-04 2016-03-03 1 2015-07-23 2017-01-05 TRUE
28: 3 2 2018-09-03 2016-03-04 2016-04-03 1 2015-07-23 2017-01-05 TRUE
29: 3 2 2018-09-03 2016-04-04 2016-05-03 1 2015-07-23 2017-01-05 TRUE
30: 3 2 2018-09-03 2016-05-04 2016-06-03 1 2015-07-23 2017-01-05 TRUE
31: 3 2 2018-09-03 2016-06-04 2016-07-03 1 2015-07-23 2017-01-05 TRUE
32: 3 2 2018-09-03 2016-07-04 2016-08-03 1 2015-07-23 2017-01-05 TRUE
33: 3 2 2018-09-03 2016-08-04 2016-09-03 1 2015-07-23 2017-01-05 TRUE
34: 3 2 2018-09-03 2016-09-04 2016-10-03 1 2015-07-23 2017-01-05 TRUE
35: 3 2 2018-09-03 2016-10-04 2016-11-03 1 2015-07-23 2017-01-05 TRUE
36: 3 2 2018-09-03 2016-11-04 2016-12-03 1 2015-07-23 2017-01-05 TRUE
37: 3 2 2018-09-03 2016-12-04 2017-01-03 1 2015-07-23 2017-01-05 TRUE
38: 3 2 2018-09-03 2017-01-04 2017-02-03 1 2015-07-23 2017-01-05 TRUE
39: 1 2 2018-09-03 2017-02-04 2017-03-03 NA <NA> <NA> FALSE
40: 1 2 2018-09-03 2017-03-04 2017-04-03 NA <NA> <NA> FALSE
41: 1 2 2018-09-03 2017-04-04 2017-05-03 NA <NA> <NA> FALSE
42: 1 2 2018-09-03 2017-05-04 2017-06-03 NA <NA> <NA> FALSE
43: 1 2 2018-09-03 2017-06-04 2017-07-03 NA <NA> <NA> FALSE
44: 1 2 2018-09-03 2017-07-04 2017-08-03 NA <NA> <NA> FALSE
45: 1 2 2018-09-03 2017-08-04 2017-09-03 NA <NA> <NA> FALSE
46: 1 2 2018-09-03 2017-09-04 2017-10-03 NA <NA> <NA> FALSE
47: 1 2 2018-09-03 2017-10-04 2017-11-03 NA <NA> <NA> FALSE
48: 1 2 2018-09-03 2017-11-04 2017-12-03 NA <NA> <NA> FALSE
49: 1 2 2018-09-03 2017-12-04 2018-01-03 NA <NA> <NA> FALSE
50: 1 2 2018-09-03 2018-01-04 2018-02-03 NA <NA> <NA> FALSE
51: 3 2 2018-09-03 2018-02-04 2018-03-03 2 2018-02-13 2018-06-04 TRUE
52: 3 2 2018-09-03 2018-03-04 2018-04-03 2 2018-02-13 2018-06-04 TRUE
53: 3 2 2018-09-03 2018-04-04 2018-05-03 2 2018-02-13 2018-06-04 TRUE
54: 3 2 2018-09-03 2018-05-04 2018-06-03 2 2018-02-13 2018-06-04 TRUE
55: 3 2 2018-09-03 2018-06-04 2018-07-03 2 2018-02-13 2018-06-04 TRUE
56: 1 2 2018-09-03 2018-07-04 2018-08-03 NA <NA> <NA> FALSE
57: 1 2 2018-09-03 2018-08-04 2018-09-03 NA <NA> <NA> FALSE
.join id end_date month_start month_end spell start end in_care