Locates 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)

Arguments

domain

Any SDTM domain data frame, with USUBJID and a date variable.

se

Subject Elements, with USUBJID, SESTDTC, EPOCH and usually SEENDTC — typically the output of derive_se() called with ta.

dtc_var

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.

Value

domain as an sdtmgap_domain tibble with an EPOCH column added.

Boundary rule

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.

Partial dates

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.

See also

Examples

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>