derive_epoch.RdLocates each record's date inside that subject's element intervals from SE
and copies the element's EPOCH onto the record. This is an interval join by
USUBJID, not a merge on a key, which is why it cannot be written as a
one-line dplyr::left_join().
derive_epoch(domain, se, dtc_var = NULL)Any SDTM domain data frame, with USUBJID and a date variable.
Subject Elements, with USUBJID, SESTDTC, EPOCH and usually
SEENDTC — typically the output of derive_se() called with ta.
Name of the date variable to place. NULL (default) infers
the domain's standard --DTC from its DOMAIN value, falling back to
--STDTC for domains such as AE and EX that have no --DTC.
domain as an sdtmgap_domain tibble with an EPOCH column added.
Both ends of an element are inclusive: a record matches when
SESTDTC <= date <= SEENDTC. Because SDTMIG elements are contiguous
(SEENDTC of one element equals SESTDTC of the next), a record falling
exactly on a shared boundary matches two elements; the one with the later
SESTDTC wins. A first-dose visit on the day screening ends is therefore
TREATMENT, not SCREENING, which is what a reviewer expects. A missing
SEENDTC is treated as open-ended, so records after the last element still
get its EPOCH rather than nothing.
A record's date is matched at its earliest possible instant, so "2026-02"
is placed as if it were 2026-02-01. Element boundaries are widened the
other way — a partial SESTDTC starts as early as it could, a partial
SEENDTC ends as late as it could — so a partial date is never excluded from
an element it might genuinely belong to.
Records whose date is missing or unparseable, whose subject has no elements,
or that fall in no interval come back with EPOCH missing. They are never
dropped: the returned data frame has exactly the rows of domain, in the
same order.
SAS idiom replaced. The PROC SQL non-equi join
(on a.usubjid = b.usubjid and a.--dtc between b.sestdtc and b.seendtc) or
the equivalent hash-object lookup, plus the tie-break logic that is usually
left out and found in validation.
se <- derive_se(sdtmgap_example("edge_dm"), sdtmgap_example("edge_elements"),
sdtmgap_example("edge_te"), sdtmgap_example("edge_ta"))
vs <- derive_epoch(sdtmgap_example("edge_vs"), se)
table(vs$EPOCH, useNA = "ifany")
#>
#> FOLLOW-UP SCREENING TREATMENT <NA>
#> 6 6 17 3
# AE-style domains have no --DTC; --STDTC is found automatically,
# or name it yourself:
derive_epoch(sdtmgap_example("edge_lb"), se, dtc_var = "LBDTC")
#>
#> ── SDTM LB (6 records) ─────────────────────────────────────────────────────────
#> ℹ EPOCH assigned from LBDTC for 6 of 6 record(s).
#> # A tibble: 6 × 12
#> STUDYID DOMAIN USUBJID LBSEQ LBTESTCD LBTEST LBORRES LBORRESU VISITNUM VISIT
#> * <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr> <dbl> <chr>
#> 1 SDTMGAP01 LB SDTMGA… 1 ALT Alani… 22 U/L 1 SCRE…
#> 2 SDTMGAP01 LB SDTMGA… 2 ALT Alani… 25 U/L 3 WEEK…
#> 3 SDTMGAP01 LB SDTMGA… 1 ALT Alani… 31 U/L 1 SCRE…
#> 4 SDTMGAP01 LB SDTMGA… 2 ALT Alani… 29 U/L 3 WEEK…
#> 5 SDTMGAP01 LB SDTMGA… 1 ALT Alani… 18 U/L 1 SCRE…
#> 6 SDTMGAP01 LB SDTMGA… 2 ALT Alani… 19 U/L 3 WEEK…
#> # ℹ 2 more variables: LBDTC <chr>, EPOCH <chr>