{ints} helps to tidy up messy intervals. It can group and merge overlapping intervals as well as remove parts of intervals, based on other intervals.
{ints} uses {data.table} for memory efficiency and speed, it will always return a data.table object.
Installation
You can install the development version of {ints} from GitHub with:
pak::pak("ds-turner/ints")Useage
Grouping and Merging Intervals
We can group overlapping intervals by using grp_ints. It returns the same data but add an id for intervals that overlap
ints <- data.frame(
id = c("A", "A", "A", "B", "B", "B"),
st = c(1, 2, 6, 10, 12, 14),
end = c(3, 4, 7, 11, 14, 16)
)
ints
#> id st end
#> 1 A 1 3
#> 2 A 2 4
#> 3 A 6 7
#> 4 B 10 11
#> 5 B 12 14
#> 6 B 14 16
grp_ints(ints, st, end, id)
#> id st end int_grp_id
#> <char> <num> <num> <int>
#> 1: A 1 3 1
#> 2: A 2 4 1
#> 3: A 6 7 2
#> 4: B 10 11 3
#> 5: B 12 14 3
#> 6: B 14 16 3We can also merge overlapping intervals together using merge_ints. It will return the interval start and end, the interval group id and any grouping variables used.
merge_ints(ints, st, end, id)
#> id int_grp_id st end
#> <char> <int> <num> <num>
#> 1: A 1 1 4
#> 2: A 2 6 7
#> 3: B 3 10 16Triming Intervals
We can remove parts of intervals that overlap with another intervals.
Below is an example of some treatment data. Each patient has a period of monthly, weekly and daily treatments. The different dose frequencies should be mutually exclusive.
trt <- data.frame(
pat_id = c(1, 1, 1,
2, 2, 2
),
trt_start_date = as.Date(c(
"2023-07-13", "2023-06-05", "2023-01-01",
"2023-03-02", "2023-03-05", "2023-01-01"
)),
trt_end_date = as.Date(c(
"2023-07-20", "2023-08-15", "2023-12-31",
"2023-03-10", "2023-03-15", "2023-12-31"
)),
dose_freq = c(
"daily", "weekly", "monthly",
"daily", "weekly", "monthly"
)
)
trt
#> pat_id trt_start_date trt_end_date dose_freq
#> 1 1 2023-07-13 2023-07-20 daily
#> 2 1 2023-06-05 2023-08-15 weekly
#> 3 1 2023-01-01 2023-12-31 monthly
#> 4 2 2023-03-02 2023-03-10 daily
#> 5 2 2023-03-05 2023-03-15 weekly
#> 6 2 2023-01-01 2023-12-31 monthlyOur aim is to remove overlaps in the treatment data without merging the intervals. When an interval overlaps with another interval of a different frequency, we want to remove the overlapping part from the interval with the lower frequency, making the dose frequencies mutually exclusive.
# create a list with a separate dataset for each frequency
trt_list <- split(trt, trt$dose_freq)
# remove the parts of the weekly dosing periods that overlap with the daily
# dosing intervals
trt_list$weekly <- trm_ints(
trt_list$weekly,
trt_list$daily,
trt_start_date,
trt_end_date,
trt_start_date,
trt_end_date,
pat_id,
.gap = 1
)
# remove the parts of the monthly dosing periods that overlap with the daily and
# weekly dosing intervals
trt_list$monthly <- trm_ints(
trt_list$monthly,
rbind(trt_list$daily, trt_list$weekly),
trt_start_date,
trt_end_date,
trt_start_date,
trt_end_date,
pat_id,
.gap = 1
)
# bind the results together and order
trt2 <- data.table::rbindlist(trt_list)
data.table::setorder(trt2, pat_id, trt_start_date)
trt2
#> pat_id trt_start_date trt_end_date dose_freq
#> <num> <Date> <Date> <char>
#> 1: 1 2023-01-02 2023-06-05 monthly
#> 2: 1 2023-06-06 2023-07-12 weekly
#> 3: 1 2023-07-13 2023-07-20 daily
#> 4: 1 2023-07-21 2023-08-14 weekly
#> 5: 1 2023-08-15 2023-12-30 monthly
#> 6: 2 2023-01-02 2023-03-01 monthly
#> 7: 2 2023-03-02 2023-03-10 daily
#> 8: 2 2023-03-11 2023-03-14 weekly
#> 9: 2 2023-03-15 2023-12-30 monthlyNegative Intervals
We can also create a set of intervals that represent the gaps between the given intervals
ints <- data.frame(
id = c("a", "a", "a", "a", "a", "b", "b", "c", "c", "c", "c", "d"),
start = c(1, 4, 10, 18, 23, 7, 12, 1, 7, 12, 23, 10),
end = c(3, 7, 15, 21, 25, 9, 16, 3, 9, 16, 25, 15),
index = c(5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5),
study_end = c(20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20)
)
ints
#> id start end index study_end
#> 1 a 1 3 5 20
#> 2 a 4 7 5 20
#> 3 a 10 15 5 20
#> 4 a 18 21 5 20
#> 5 a 23 25 5 20
#> 6 b 7 9 5 20
#> 7 b 12 16 5 20
#> 8 c 1 3 5 20
#> 9 c 7 9 5 20
#> 10 c 12 16 5 20
#> 11 c 23 25 5 20
#> 12 d 10 15 5 20Create a set of intervals between the intervals in ints.
neg_ints(ints, start, end, id)
#> id start end
#> <char> <num> <num>
#> 1: a 8 9
#> 2: a 16 17
#> 3: a 22 22
#> 4: b 10 11
#> 5: c 4 6
#> 6: c 10 11
#> 7: c 17 22Set a lower and upper bound for the new intervals.
neg_ints(ints, start, end, id, .lower = index, .upper = study_end)
#> id start end index study_end
#> <char> <num> <num> <num> <num>
#> 1: a 8 9 5 20
#> 2: a 16 17 5 20
#> 3: b 5 6 5 20
#> 4: b 10 11 5 20
#> 5: b 17 20 5 20
#> 6: c 4 6 5 20
#> 7: c 10 11 5 20
#> 8: c 17 22 5 20
#> 9: d 5 9 5 20
#> 10: d 16 20 5 20