derive_se.RdCollapses element-level source records (exposure, disposition, or a stack of
both) into one SE record per subject per element, resolves the element label
from Trial Elements (TE), orders the elements chronologically, and numbers
them with SESEQ.
derive_se(dm, exposure, te, ta = NULL)Demographics (DM). Supplies the subject universe, STUDYID, and
the arm (ACTARMCD if present, else ARMCD) used to join ta. Subjects
absent from dm are dropped; subjects with no source records get no SE
records.
Element source records: one or more rows per subject and
element, with USUBJID, a --STDTC (and optionally --ENDTC) date
variable, and ETCD. Build it from EX, DS, or both. When ETCD is absent
it is derived from ta — see the ETCD section below.
Trial Elements (TE), with ETCD, ELEMENT and optionally TEDUR.
Optional Trial Arms (TA), with ARMCD, ETCD and optionally
TAETORD and EPOCH. Supplying it adds TAETORD and EPOCH to the
result, and EPOCH is what derive_epoch() needs. EPOCH is joined on
arm and element; where the subject's arm is not in ta at all — screen
failures usually are not — it falls back to the element alone, but only
when every arm containing that element agrees on its EPOCH. Elements
ta does not place (CDISCPILOT01 defines no follow-up element in TA) keep
EPOCH missing rather than being assigned a plausible one.
An sdtmgap_domain tibble (a plain data frame downstream) with
STUDYID, DOMAIN, USUBJID, SESEQ, ETCD, ELEMENT, SESTDTC,
SEENDTC, plus TAETORD and EPOCH when ta is supplied.
SDTMIG treats subject elements as contiguous: the end of one element is the
start of the next. SEENDTC is therefore filled, in this order:
the latest observed end date among that subject's source records for the element;
failing that, the SESTDTC of the subject's next element;
failing that, SESTDTC plus TEDUR from te (inclusive of the start
day, so P14D from 2026-02-18 ends 2026-03-03);
failing that, NA — an open-ended final element, which
derive_epoch() treats as running to the end of time rather than
dropping the records after it.
Partial dates are ordered by their earliest possible instant, and are written
through to SESTDTC/SEENDTC exactly as collected (a "2026-02" end stays
"2026-02"); see derive_epoch() for how they are then matched.
If exposure has no ETCD column it is derived from ta by
derive_etcd(), which places each dosing record on the arm's treatment
elements in order. That requires ta; without it, or where the design does
not determine the assignment, derive_se() aborts naming what is missing
rather than guessing. Read derive_etcd() before relying on it — the rule is
ordering-based, and a design that distinguishes its treatment elements by
dose or branch instead needs ETCD attached by hand.
Step 3 above adds an ISO 8601 duration to SESTDTC. Components are applied
largest first — years, then months, then weeks and days — and the start date
counts as day 1, so one day is subtracted at the end. Years and months are
calendar arithmetic, not a fixed number of days: P1M from 2026-01-15 ends
2026-02-14, and P1M from 2026-01-31 ends 2026-02-27, because the
month step is clamped to the last day of the target month (2026-02-28)
rather than overflowing into March. Weeks and days are fixed at 7 and 1 day.
Composite durations work: P1M15D from 2026-01-20 is one month to
2026-02-20, then 15 days to 2026-03-07, inclusive end 2026-03-06.
A duration with a time component (PT12H, P1DT12H), the alternate
P0003-06-04 form, an all-zero duration, or a partial SESTDTC yields NA —
sdtmgap leaves the element open-ended rather than inventing an end date.
Limit. An element start that the source records do give is never moved.
A study that snaps each element back to start on the day the previous one
ended will therefore differ from sdtmgap by the gap between the two — one
day, wherever dosing resumes the day after the previous element's last dose.
Records landing in that gap are the main source of element-level
disagreement; see the README for the rate measured against CDISCPILOT01.
SAS idiom replaced. The hand-written PROC SORT + BY USUBJID ETCD +
RETAIN/LAG DATA step that every study writes to stitch element start and
stop dates together and carry the next element's start backwards.
se <- derive_se(
dm = sdtmgap_example("edge_dm"),
exposure = sdtmgap_example("edge_elements"),
te = sdtmgap_example("edge_te"),
ta = sdtmgap_example("edge_ta")
)
se[se$USUBJID == "SDTMGAP01-001", c("SESEQ", "ETCD", "SESTDTC", "SEENDTC", "EPOCH")]
#>
#> ── SDTM SE (3 records) ─────────────────────────────────────────────────────────
#> ℹ 6 subject(s) with elements, 17 element record(s).
#> ℹ 1 subject(s) in DM contributed no elements.
#> ℹ 1 source record(s) had a partial start date.
#> # A tibble: 3 × 5
#> SESEQ ETCD SESTDTC SEENDTC EPOCH
#> <int> <chr> <chr> <chr> <chr>
#> 1 1 SCRN 2026-01-04 2026-01-19 SCREENING
#> 2 2 TRTA 2026-01-19 2026-02-15 TREATMENT
#> 3 3 FUP 2026-02-16 2026-03-01 FOLLOW-UP
# ETCD derived from Trial Arms rather than supplied: the CDISCPILOT01
# exposure records carry no element code.
derive_se(
dm = sdtmgap_example("lzzt_dm"),
exposure = sdtmgap_example("lzzt_ex"),
te = sdtmgap_example("lzzt_te"),
ta = sdtmgap_example("lzzt_ta")
)
#>
#> ── SDTM SE (354 records) ───────────────────────────────────────────────────────
#> ℹ 254 subject(s) with elements, 354 element record(s).
#> ℹ 52 subject(s) in DM contributed no elements.
#> # A tibble: 354 × 10
#> STUDYID DOMAIN USUBJID SESEQ ETCD ELEMENT SESTDTC SEENDTC TAETORD EPOCH
#> * <chr> <chr> <chr> <int> <chr> <chr> <chr> <chr> <dbl> <chr>
#> 1 CDISCPILOT01 SE 01-701… 1 PBO Placebo 2014-0… 2014-0… 2 Trea…
#> 2 CDISCPILOT01 SE 01-701… 1 PBO Placebo 2012-0… 2012-0… 2 Trea…
#> 3 CDISCPILOT01 SE 01-701… 1 HIS High_S… 2013-0… 2013-0… 2 Trea…
#> 4 CDISCPILOT01 SE 01-701… 2 HIM High_M… 2013-0… 2014-0… 3 Trea…
#> 5 CDISCPILOT01 SE 01-701… 3 HIE High_E… 2014-0… 2014-0… 4 Trea…
#> 6 CDISCPILOT01 SE 01-701… 1 LO Low 2014-0… 2014-0… 2 Trea…
#> 7 CDISCPILOT01 SE 01-701… 1 HIS High_S… 2014-0… 2014-0… 2 Trea…
#> 8 CDISCPILOT01 SE 01-701… 2 HIM High_M… 2014-0… 2014-1… 3 Trea…
#> 9 CDISCPILOT01 SE 01-701… 3 HIE High_E… 2014-1… 2014-1… 4 Trea…
#> 10 CDISCPILOT01 SE 01-701… 1 PBO Placebo 2013-0… 2013-0… 2 Trea…
#> # ℹ 344 more rows