collapse_ae_episodes.RdRecords for the same subject and preferred term whose date ranges overlap,
touch, or are separated by no more than gap_days are collapsed into a
single episode carrying the earliest onset, the latest resolution, the worst
severity, the worst causality and the number of records collapsed.
collapse_ae_episodes(
adae,
gap_days = 1,
by = c("USUBJID", "AEDECOD"),
start_var = "ASTDT",
end_var = "AENDT",
severity_var = "AESEV",
causality_var = "AEREL",
severity_levels = c("MILD", "MODERATE", "SEVERE", "LIFE THREATENING", "FATAL"),
causality_levels = c("NONE", "NOT RELATED", "UNLIKELY", "REMOTE", "POSSIBLE",
"PROBABLE", "RELATED", "DEFINITE")
)ADaM adverse event data frame, e.g. ADAE.
Maximum number of days between one episode's end and the
next record's start for the two to be considered the same episode. 1
(the default) merges records on consecutive days.
Character vector of grouping variables. Default
c("USUBJID", "AEDECOD").
Onset and resolution date columns. Date,
POSIXct, ISO date character ("2021-01-05") or numeric study days are
accepted. Defaults "ASTDT" / "AENDT".
Severity and causality columns, or NULL
to skip. Defaults "AESEV" / "AEREL".
Character vectors ordering the severity and causality vocabularies from least to most severe.
A tibble::tibble, one row per episode, with the by variables and
EPISODE (sequence within the by group), start_var (earliest onset),
end_var (latest resolution, NA if any record is ongoing),
severity_var, causality_var, NRECORD (records collapsed) and
ONGOING (logical). A plain data frame, ready to hand on to admiral.
Within each by group records are sorted by start date and merged
left to right. A record starts a new episode when
so with the default gap_days = 1:
overlapping (05-10 Jan and 08-15 Jan) merge, episode 05-15 Jan;
adjacent (01-03 Feb and 04-06 Feb, i.e. no clear day between) merge;
gapped (01-02 Mar and 10-12 Mar) stay separate;
open-ended (a record with a missing end date, i.e. still ongoing)
absorbs every later record in the group; the episode end is NA and
ONGOING is TRUE;
gap_days = 0 merges only records that overlap or touch.
Merging uses a running maximum end date, not the previous record's end, so a long record that swallows several short ones is handled correctly.
Records with a missing start date cannot be positioned on the timeline; each is returned as its own single-record episode, sorted last within its group, and a warning reports how many. A record whose end date precedes its start date is treated as ending on its start date.
"Worst" is the value with the highest rank in severity_levels /
causality_levels (compared upper-case, whitespace-trimmed). Values not in
the level list rank below every listed value and trigger a warning; missing
values never win unless every record in the episode is missing.
The %AE_EPISODE / "collapse continuing AEs" DATA step: PROC SORT by
USUBJID AEDECOD ASTDT, then a BY-group RETAIN of _prev_end with
IF ASTDT - _prev_end > 1 THEN episode + 1; and a second pass for
MAX(AESEVN). The RETAIN version is the derivation most often quietly
wrong, because it compares against the previous record's end date rather
than the running maximum. See sas_note().
adae <- utils::read.csv(
system.file("extdata", "adae_example.csv", package = "admiralease")
)
collapse_ae_episodes(adae)
#> ! 1 record with a missing ASTDT cannot be merged; kept as single-record episode.
#> # A tibble: 7 × 9
#> USUBJID AEDECOD EPISODE ASTDT AENDT AESEV AEREL NRECORD ONGOING
#> <chr> <chr> <int> <date> <date> <chr> <chr> <int> <lgl>
#> 1 AE01-001 ERYTHEMA 1 2021-01-05 2021-01-15 MODERA… POSS… 2 FALSE
#> 2 AE01-001 HEADACHE 1 2021-03-01 2021-03-02 MILD NONE 1 FALSE
#> 3 AE01-001 HEADACHE 2 2021-03-10 2021-03-12 SEVERE PROB… 1 FALSE
#> 4 AE01-002 DIARRHOEA 1 2021-04-01 NA SEVERE PROB… 2 TRUE
#> 5 AE01-002 NAUSEA 1 2021-02-01 2021-02-06 MODERA… POSS… 2 FALSE
#> 6 AE01-003 DIZZINESS 1 NA 2021-06-05 MODERA… POSS… 1 FALSE
#> 7 AE01-003 FATIGUE 1 2021-05-01 2021-05-25 SEVERE PROB… 3 FALSE
# Only merge records that actually overlap or touch
collapse_ae_episodes(adae, gap_days = 0)
#> ! 1 record with a missing ASTDT cannot be merged; kept as single-record episode.
#> # A tibble: 9 × 9
#> USUBJID AEDECOD EPISODE ASTDT AENDT AESEV AEREL NRECORD ONGOING
#> <chr> <chr> <int> <date> <date> <chr> <chr> <int> <lgl>
#> 1 AE01-001 ERYTHEMA 1 2021-01-05 2021-01-15 MODERA… POSS… 2 FALSE
#> 2 AE01-001 HEADACHE 1 2021-03-01 2021-03-02 MILD NONE 1 FALSE
#> 3 AE01-001 HEADACHE 2 2021-03-10 2021-03-12 SEVERE PROB… 1 FALSE
#> 4 AE01-002 DIARRHOEA 1 2021-04-01 NA SEVERE PROB… 2 TRUE
#> 5 AE01-002 NAUSEA 1 2021-02-01 2021-02-03 MILD REMO… 1 FALSE
#> 6 AE01-002 NAUSEA 2 2021-02-04 2021-02-06 MODERA… POSS… 1 FALSE
#> 7 AE01-003 DIZZINESS 1 NA 2021-06-05 MODERA… POSS… 1 FALSE
#> 8 AE01-003 FATIGUE 1 2021-05-01 2021-05-20 MODERA… POSS… 2 FALSE
#> 9 AE01-003 FATIGUE 2 2021-05-21 2021-05-25 SEVERE PROB… 1 FALSE
# Realistic data, if available
if (rlang::is_installed("pharmaverseadam")) {
ep <- collapse_ae_episodes(pharmaverseadam::adae)
c(records = nrow(pharmaverseadam::adae), episodes = nrow(ep))
}
#> records episodes
#> 1191 867