SDTM EPOCH Derivation Utilities - Multi-language implementations for deriving the EPOCH variable in CDISC SDTM datasets.
This repository provides derive_epoch() functions in R, Python, and SAS for deriving the EPOCH variable for SDTM datasets. The functions analyze subject event records from the SE (Subject Elements) domain and determine which epoch each record belongs to by comparing reference datetime variables against epoch datetime ranges.
- ISO 8601 Precision Handling: Properly handles date-only (YYYY-MM-DD), datetime without seconds (YYYY-MM-DDTHH:MM), and full datetime (YYYY-MM-DDTHH:MM:SS) formats
- Datetime Boundary Resolution: When dates fall exactly on epoch boundaries, uses "tightest SESTDTC match" algorithm (latest start datetime)
- SDTM-IG Compliant: Pre-study records return missing EPOCH by default, with optional edge case handling
- Comprehensive Testing: Full test suites in testthat (R), pytest (Python), and native SAS
sdtm-epoch/
├── r/
│ └── derive_epoch.R # R implementation
├── python/
│ └── derive_epoch.py # Python implementation
├── sas/
│ └── derive_epoch.sas # SAS macro implementation
├── tests/
│ ├── testthat/
│ │ └── test-derive_epoch.R # R testthat tests
│ ├── pytest/
│ │ └── test_derive_epoch.py # Python pytest tests
│ └── sas/
│ └── test_derive_epoch.sas # SAS test program
├── README.md
├── LICENSE
└── .gitignore
# Source the function directly
source("r/derive_epoch.R")
# Required packages
install.packages(c("dplyr", "cli"))# Import the function
from python.derive_epoch import derive_epoch
# Required packages
# pip install pandas/* Include the macro */
%include "sas/derive_epoch.sas";library(dplyr)
source("r/derive_epoch.R")
# Load SE domain (must be named 'SE' in environment)
SE <- data.frame(
USUBJID = c("S001", "S001"),
SESTDY = c(1, 30),
SEENDY = c(29, 60),
SESTDTC = c("2024-01-04", "2024-02-07T16:10"),
SEENDTC = c("2024-02-07T16:10", "2025-05-13T15:50"),
EPOCH = c("SCREENING", "TREATMENT"),
TAETORD = c(1, 2)
)
# Create input dataset
dm <- data.frame(
USUBJID = "S001",
RFSTDTC = "2024-02-07T16:00"
)
# Derive EPOCH
result <- derive_epoch(dm, "RFSTDTC")
# result$EPOCH == "SCREENING" (16:00 is before 16:10 boundary)import pandas as pd
from python.derive_epoch import derive_epoch
# Load SE domain (must be named 'SE' in global namespace)
SE = pd.DataFrame({
'USUBJID': ['S001', 'S001'],
'SESTDY': [1, 30],
'SEENDY': [29, 60],
'SESTDTC': ['2024-01-04', '2024-02-07T16:10'],
'SEENDTC': ['2024-02-07T16:10', '2025-05-13T15:50'],
'EPOCH': ['SCREENING', 'TREATMENT'],
'TAETORD': [1, 2]
})
# Create input dataset
dm = pd.DataFrame({
'USUBJID': ['S001'],
'RFSTDTC': ['2024-02-07T16:00']
})
# Derive EPOCH
result = derive_epoch(dm, 'RFSTDTC')
# result['EPOCH'].iloc[0] == 'SCREENING'/* SE domain must exist in sdtm library */
libname sdtm "path/to/sdtm";
%include "sas/derive_epoch.sas";
/* Derive EPOCH for AE domain */
%derive_epoch(
sdtm_in = sdtm.ae,
sdtm_out = ae_with_epoch,
ref_var = aestdtc
);| Parameter | R | Python | SAS | Description |
|---|---|---|---|---|
sdtm_in |
✓ | ✓ | ✓ | Input SDTM dataset with USUBJID and ref_var |
ref_var |
✓ | ✓ | ✓ | Reference datetime column (ISO 8601) |
handle_edge |
✓ | ✓ | ✓ | Handle out-of-range dates (default: FALSE/N) |
sdtm_out |
- | - | ✓ | Output dataset name (SAS only) |
The SE (Subject Elements) domain must contain:
USUBJID- Unique Subject IdentifierSESTDY- SE Start DaySEENDY- SE End DaySESTDTC- SE Start Date/Time (ISO 8601)SEENDTC- SE End Date/Time (ISO 8601)EPOCH- Epoch valueTAETORD- Trial Arm Element Order
| Format | Start Datetime | End Datetime |
|---|---|---|
| YYYY-MM-DD | 00:00:00 | 23:59:59 |
| YYYY-MM-DDTHH:MM | :00 appended | :59 appended |
| YYYY-MM-DDTHH:MM:SS | As-is | As-is |
library(testthat)
library(dplyr)
library(cli)
source("r/derive_epoch.R")
test_file("tests/testthat/test-derive_epoch.R")cd sdtm-epoch
pytest tests/pytest/test_derive_epoch.py -v/* Run in SAS environment */
%include "tests/sas/test_derive_epoch.sas";- Findings domains: Use
--DTCasref_var(e.g., LBDTC, VSDTC) - Interventions/Events domains: Use
--STDTCasref_var(e.g., AESTDTC, EXSTDTC) - Pre-study records: Should have null EPOCH (use
handle_edge = FALSE)
Contributions are welcome! Please ensure:
- Tests pass for all three languages
- Follow existing code style
- Update documentation as needed
Saikrishnareddy Yengannagari - GitHub
MIT License - see LICENSE