$npx -y skills add RConsortium/pharma-skills --skill sdtm-oakDerives CDISC SDTM domains from raw clinical (EDC/eCRF) data using the {sdtm.oak} R package. Use when a user needs to map raw study data to SDTM Events (AE, CM, MH), Findings (VS, LB, EG), or Interventions (EX) domains following the sdtm.oak algorithm framework. Produces executab
| 1 | # sdtm-oak |
| 2 | |
| 3 | Derives CDISC SDTM domains from raw clinical data using the {sdtm.oak} |
| 4 | algorithm framework. Outputs executable R code with full derivation traceability. |
| 5 | |
| 6 | See [`references/oak-functions.md`](references/oak-functions.md) for the full |
| 7 | function reference. |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## Inputs |
| 12 | |
| 13 | Before generating code, confirm: |
| 14 | |
| 15 | | Input | Required | Notes | |
| 16 | |---|---|---| |
| 17 | | Raw EDC dataset | Yes | e.g. `ae_raw`, `vs_raw` — raw CRF form data | |
| 18 | | CT specification | Yes | CSV in CDISC codelist format; load via `read_ct_spec()` | |
| 19 | | Domain specification | Yes | Variable list, CT codelists per variable, date formats | |
| 20 | | DM domain | For study day / BLFL | Provides RFSTDTC for `derive_study_day()` and `derive_blfl()` | |
| 21 | |
| 22 | **Always inspect the raw dataset first** — raw column names vary by EDC system |
| 23 | and study. Print `names(raw_dat)` and `head(raw_dat)` before writing any |
| 24 | derivations. |
| 25 | |
| 26 | --- |
| 27 | |
| 28 | ## Core algorithms |
| 29 | |
| 30 | sdtm.oak provides six mapping algorithms. Choose based on whether the target |
| 31 | variable has controlled terminology (CT) and whether the value is derived from |
| 32 | raw data or hardcoded. |
| 33 | |
| 34 | | Algorithm | CT? | Source | Use for | |
| 35 | |---|---|---|---| |
| 36 | | `assign_no_ct()` | No | Raw column | Free-text variables: AETERM, CMTRT, VSORRES | |
| 37 | | `assign_ct()` | Yes | Raw column | CT-mapped from raw: AESEV, AESER, SEX, RACE | |
| 38 | | `hardcode_no_ct()` | No | Fixed value | Study-constant free-text: STUDYID, custom flags | |
| 39 | | `hardcode_ct()` | Yes | Fixed value | Domain constants validated against CT: DOMAIN | |
| 40 | | `assign_datetime()` | — | Raw date col(s) | Any `--DTC` variable: AESTDTC, VSDTC, EXSTDTC | |
| 41 | | `condition_add()` | — | Condition expr | Gate any of the above to a row subset | |
| 42 | |
| 43 | All six functions share the same `id_vars` join key (default: `oak_id_vars()`) |
| 44 | and the same `tgt_dat` pipe pattern — pass the growing SDTM dataset as |
| 45 | `tgt_dat` to accumulate variables. |
| 46 | |
| 47 | --- |
| 48 | |
| 49 | ## Workflow |
| 50 | |
| 51 | Follow these steps in order. Write code section by section, not as a single block. |
| 52 | |
| 53 | ### Step 1 — Setup and data inspection |
| 54 | |
| 55 | ```r |
| 56 | library(sdtm.oak) |
| 57 | library(dplyr) |
| 58 | |
| 59 | # Load raw data — replace with actual source |
| 60 | ae_raw <- <load raw AE data> # e.g. sdtm.oak::ae_raw for the package example |
| 61 | |
| 62 | # ALWAYS inspect before writing derivations |
| 63 | cat("Columns:\n"); print(names(ae_raw)) |
| 64 | cat("Rows:", nrow(ae_raw), "\n") |
| 65 | print(head(ae_raw, 3)) |
| 66 | ``` |
| 67 | |
| 68 | ### Step 2 — Generate oak ID variables |
| 69 | |
| 70 | `generate_oak_id_vars()` adds three key columns used as join keys throughout |
| 71 | all subsequent derivations. Call this once on the raw dataset. |
| 72 | |
| 73 | ```r |
| 74 | # REVIEW: Set pat_var to the column holding the subject/patient identifier |
| 75 | # in this raw dataset. Set raw_src to a stable label for the CRF form. |
| 76 | ae_oak <- generate_oak_id_vars( |
| 77 | raw_dat = ae_raw, |
| 78 | pat_var = "patient_number", # confirm column name from Step 1 inspection |
| 79 | raw_src = "AE_FORM" # stable label for this raw source |
| 80 | ) |
| 81 | # Adds: oak_id (row key), raw_source (form label), patient_number (subj ID) |
| 82 | ``` |
| 83 | |
| 84 | ### Step 3 — Load controlled terminology |
| 85 | |
| 86 | ```r |
| 87 | # REVIEW: Replace path with the study CT spec CSV. |
| 88 | # For the sdtm.oak package example data, use read_ct_spec_example(). |
| 89 | ct_spec <- read_ct_spec_example() # or: read_ct_spec("path/to/ct_spec.csv") |
| 90 | |
| 91 | # Validate before use |
| 92 | assert_ct_spec(ct_spec) |
| 93 | ``` |
| 94 | |
| 95 | ### Step 4 — Hardcode domain constants |
| 96 | |
| 97 | Fixed values that apply to every record in the domain. Use `hardcode_ct()` for |
| 98 | values validated against CT (DOMAIN); use `hardcode_no_ct()` for free-text constants. |
| 99 | |
| 100 | ```r |
| 101 | ae_domain <- ae_oak |> |
| 102 | # DOMAIN is a CT-controlled variable — use hardcode_ct |
| 103 | hardcode_ct( |
| 104 | tgt_var = "DOMAIN", |
| 105 | tgt_val = "AE", |
| 106 | raw_dat = ae_raw, |
| 107 | raw_var = "AETERM", # presence filter: only rows with a non-NA AE term |
| 108 | ct_spec = ct_spec, |
| 109 | ct_clst = "DOMAIN" |
| 110 | ) |
| 111 | ``` |
| 112 | |
| 113 | ### Step 5 — Assign free-text variables (assign_no_ct) |
| 114 | |
| 115 | Use for variables with no CT restriction — raw text carried directly. |
| 116 | |
| 117 | ```r |
| 118 | ae_domain <- ae_domain |> |
| 119 | assign_no_ct( |
| 120 | tgt_var = "AETERM", |
| 121 | raw_dat = ae_raw, |
| 122 | raw_var = "ae_term" # REVIEW: confirm raw column name |
| 123 | ) |> |
| 124 | assign_no_ct( |
| 125 | tgt_var = "AELOC", |
| 126 | raw_dat = ae_raw, |
| 127 | raw_var = "ae_location" |
| 128 | ) |
| 129 | ``` |
| 130 | |
| 131 | ### Step 6 — Assign CT-mapped variables (assign_ct) |
| 132 | |
| 133 | Use for variables whose values must be recoded to CDISC |