
Migrating from metacore + xportr + Pinnacle 21
Source:vignettes/migration-guide.Rmd
migration-guide.RmdIf your team uses metacore + xportr + Pinnacle 21, this guide shows how to migrate to herald. The migration is usually a find-and-replace across your pipeline scripts — the concepts map 1:1, but the API is simpler and the toolchain collapses from three packages (plus Java) to one.
Why migrate?
| Concern | metacore + xportr + P21 | herald |
|---|---|---|
| Dependencies | 3 R packages + Java (P21) | 1 R package |
| Compiled code | xportr uses Rcpp | Pure R, no compilation |
| Auditability | Multiple source code bases | Single auditable codebase |
| Define-XML | Requires P21 Enterprise (license) | write_define_xml() |
| Validation | Requires P21 Community/Enterprise (Java) | validate() |
| Format | XPT only | XPT + Dataset-JSON v1.1 |
| ARM support | P21 Enterprise only | Included |
| Submission packaging | Manual SOP | submit() |
| CRAN-ready | metacore/xportr on CRAN | herald (pre-CRAN, GitHub) |
Function mapping
| Task | metacore + xportr | herald |
|---|---|---|
| Build spec object | metacore::metacore(ds_spec, var_spec, ...) |
herald_spec(ds_spec, var_spec, ...) |
| Read P21 Excel spec | metacore::spec_to_metacore("spec.xlsx") |
read_spec("spec.xlsx") |
| Read Define-XML spec | metacore::spec_to_metacore("define.xml") |
read_spec("define.xml") |
| Save spec to file | (not available) | write_spec(spec, "spec.json") |
| List datasets | metacore$ds_spec$dataset |
spec_datasets(spec) |
| Get variable metadata | metacore$var_spec %>% filter(dataset == "DM") |
spec_vars(spec, "DM") |
| Get codelist | metacore$codelist %>% filter(code_id == "SEX") |
spec_codelist(spec, "SEX") |
| Set variable labels | xportr::xportr_label(dm, meta, "DM") |
set_label(dm, AGE="Age", ...) |
| Coerce types | xportr::xportr_type(dm, meta, "DM") |
coerce_types(dm, spec, "DM") |
| Set lengths | xportr::xportr_length(dm, meta, "DM") |
set_length(dm, AGE=8L, ...) |
| Set formats | xportr::xportr_format(dm, meta, "DM") |
set_format(dm, AGE="8.") |
| Reorder columns | xportr::xportr_order(dm, meta, "DM") |
order_cols(dm, spec, "DM") |
| All metadata in one | 5× xportr_*() calls |
apply_spec(dm, spec, "DM") |
| Write XPT | xportr::xportr_write(dm, "dm.xpt") |
write_xpt(dm, "dm.xpt") |
| Read XPT | haven::read_xpt("dm.xpt") |
read_xpt("dm.xpt") |
| Write Dataset-JSON | (not available) | write_json(dm, "dm.json", dataset="DM") |
| Generate Define-XML | P21 Enterprise (GUI) | write_define_xml(spec, "define.xml") |
| Generate define.html | P21 Enterprise (GUI) | write_define_html(spec, "define.html") |
| Validate dataset | P21 Community/Enterprise (Java, GUI) | validate(dir, spec = spec) |
| FDA SDTM rules | P21 Enterprise license | validate(dir, config = "fda-sdtm-ig-3.3") |
| PMDA rules | P21 Enterprise + PMDA add-on | validate(dir, rules = "pmda") |
| CDISC CORE rules | P21 Enterprise | validate(dir, rules = "core") |
| HTML validation report | P21 GUI export | validation_report(result, "report.html") |
| Excel validation report | P21 GUI export | validation_report(result, "report.xlsx") |
| Package full submission | Manual SOP | submit(path, spec = spec) |
Side-by-side: SDTM DM workflow
Old way (metacore + xportr)
# 5 separate transformation steps
dm <- dm %>%
xportr::xportr_label(metacore, domain = "DM") %>%
xportr::xportr_type(metacore, domain = "DM") %>%
xportr::xportr_length(metacore, domain = "DM") %>%
xportr::xportr_format(metacore, domain = "DM") %>%
xportr::xportr_order(metacore, domain = "DM")
xportr::xportr_write(dm, path = "sdtm/dm.xpt",
metadata = metacore, domain = "DM")herald way
spec <- herald_spec(
ds_spec = data.frame(dataset = "DM", label = "Demographics",
keys = "STUDYID, USUBJID", stringsAsFactors = FALSE),
var_spec = data.frame(
dataset = c("DM","DM","DM","DM"),
variable = c("STUDYID","USUBJID","AGE","SEX"),
label = c("Study Identifier","Unique Subject Identifier","Age","Sex"),
data_type = c("text","text","integer","text"),
length = c(12L,11L,8L,1L),
order = c(1L,2L,3L,4L),
stringsAsFactors = FALSE
)
)
dm <- data.frame(
STUDYID = rep("CDISCPILOT01", 3L),
USUBJID = c("01-701-1015","01-701-1023","01-701-1028"),
AGE = c("63","64","71"), # character from CSV
SEX = c("F","M","M"),
stringsAsFactors = FALSE
)
# One call replaces all 5 xportr_* steps
dm <- suppressMessages(apply_spec(dm, spec, "DM"))
xpt_path <- file.path(tempdir(), "dm.xpt")
write_xpt(dm, xpt_path)Side-by-side: ADaM ADSL workflow
Old way (metacore + xportr + P21)
# Separate calls for each transformation
adsl <- adsl %>%
xportr::xportr_label(adam_metacore, domain = "ADSL") %>%
xportr::xportr_type(adam_metacore, domain = "ADSL") %>%
xportr::xportr_length(adam_metacore, domain = "ADSL") %>%
xportr::xportr_format(adam_metacore, domain = "ADSL") %>%
xportr::xportr_order(adam_metacore, domain = "ADSL")
# Write XPT separately
xportr::xportr_write(adsl, path = "adam/adsl.xpt",
metadata = adam_metacore, domain = "ADSL",
label = "Subject-Level Analysis Dataset")
# Define-XML: launch P21 Enterprise GUI manually
# Validate: launch P21 Community validator manuallyherald way
adam_spec <- herald_spec(
ds_spec = data.frame(
dataset = "ADSL",
label = "Subject-Level Analysis Dataset",
keys = "STUDYID, USUBJID",
stringsAsFactors = FALSE
),
var_spec = data.frame(
dataset = c("ADSL","ADSL","ADSL","ADSL"),
variable = c("STUDYID","USUBJID","TRTP","AGE"),
label = c("Study Identifier","Unique Subject Identifier",
"Planned Treatment for Period","Age"),
data_type = c("text","text","text","integer"),
length = c(12L,11L,40L,8L),
order = c(1L,2L,3L,4L),
stringsAsFactors = FALSE
)
)
adsl <- data.frame(
STUDYID = rep("CDISCPILOT01", 3L),
USUBJID = c("01-701-1015","01-701-1023","01-701-1028"),
TRTP = c("Xanomeline High Dose","Placebo","Xanomeline Low Dose"),
AGE = c(63L, 64L, 71L),
stringsAsFactors = FALSE
)
# One call
adsl <- suppressMessages(apply_spec(adsl, adam_spec, "ADSL"))
adam_dir <- tempfile("adam_")
dir.create(adam_dir)
write_xpt(adsl, file.path(adam_dir, "adsl.xpt"))
# Validate ADSL against spec
result <- validate(adam_dir, spec = adam_spec, standard = "adamig",
version = "1.1", rules = NULL)
#> ℹ Auto-selected config: "fda-adam-ig-1.1"
result$summary
#> $reject
#> [1] 0
#>
#> $high
#> [1] 0
#>
#> $medium
#> [1] 4
#>
#> $low
#> [1] 0
#>
#> $total
#> [1] 4Spec migration from existing Excel
If you already have a Pinnacle 21 Excel specification,
read_spec() reads it directly:
# Works with standard P21 Excel tab names
spec <- read_spec("path/to/existing_spec.xlsx")
# Check what was read
spec
spec_datasets(spec)All nine P21 Excel tabs are parsed: ds_spec, var_spec, value_spec, codelist, study, dictionaries, methods, comments, documents.
What to read next
-
vignette("herald")— getting started from scratch -
vignette("spec-management")— specs in depth -
vignette("metadata-helpers")—apply_spec()and individual operations -
vignette("submission-workflow")—submit()end-to-end