The specs stay the source; CDISC’s Analysis Results Standard (ARS v1.0) is written from them, and anyone’s ARS can be read back into them. The JSON is the form to exchange; CDISC’s Excel template is written for people to read.
library(tflspec)
df <- function(...) data.frame(..., stringsAsFactors = FALSE)The specs
An ARD spec, as in the article The ARD
spec, plus a report spec for the outputs’ titles. ARS needs one
thing the spec does not guess: each analysis’s
purpose (PRIMARY / SECONDARY / EXPLORATORY
OUTCOME MEASURE) is the SAP’s decision – fill the purpose
column, or give tfl_ars(purpose = ) for the rows left
blank. reason defaults to SPECIFIED IN SAP.
ard_spec <- tfl_ard_spec(list(
study = df(key = c("id", "study_id"), value = c("USUBJID", "XYZ-ONC-01")),
datasets = df(dataset = c("ADSL", "ADAE"),
path = c("data/adsl.rds", "data/adae.rds")),
populations = df(population_id = "SAF", dataset = "ADSL",
where = 'SAFFL == "Y"'),
analyses = df(
output_id = c("T-14-1-1", "T-14-1-1", "T-14-3-1"),
analysis_id = c("AGE", "SEX", "TEAE"),
method = c("continuous", "categorical", "hierarchical"),
dataset = c(NA, NA, "ADAE"),
population_id = "SAF",
where = c(NA, NA, 'TRTEMFL == "Y"'),
by = "TRT01A",
variables = c("AGE", "SEX", "AEBODSYS | AEDECOD"),
purpose = "PRIMARY OUTCOME MEASURE")))
report_spec <- tfl_table_spec(
report = df(output_id = c("T-14-1-1", "T-14-3-1")),
titles = df(output_id = c("T-14-1-1", "T-14-3-1"), line = 1,
center = c("Demographic Characteristics",
"Treatment-Emergent Adverse Events")))Writing ARS
ars <- tfl_ars(ard_spec, report_spec = report_spec)
tfl_check_ars(ars) # 0 rows: valid (with jsonvalidate: CDISC's schema too)
#> [1] part field problem
#> <0 rows> (or 0-length row.names)What ARS has no place for – and what was added to make it complete – is listed with the reason, never dropped or added silently:
knitr::kable(tfl_ars_unmapped(ars))| where | item | reason |
|---|---|---|
| T-14-1-1 | denominator | added An_T-14-1-1_GROUPN_TRT01A, the subject count by group the percentages divide by |
| T-14-3-1 | denominator | added An_T-14-3-1_GROUPN_TRT01A, the subject count by group the percentages divide by |
json <- tfl_write_ars_json(ars, file.path(tempdir(), "xyz_onc_01_ars.json"))
cat(readLines(json, n = 20), sep = "\n")
#> {
#> "id": "XYZ-ONC-01",
#> "name": "XYZ-ONC-01",
#> "version": 1,
#> "mainListOfContents": {
#> "name": "List of Planned Analyses",
#> "contentsList": {
#> "listItems": [
#> {
#> "name": "Demographic Characteristics",
#> "level": 1,
#> "order": 1,
#> "outputId": "T-14-1-1",
#> "sublist": {
#> "listItems": [
#> {
#> "name": "Number of subjects",
#> "level": 2,
#> "order": 1,
#> "analysisId": "An_T-14-1-1_GROUPN_TRT01A"tfl_write_ars_xlsx(ars, path) writes CDISC’s Excel
template, for people to read.
A report’s analysis data
An analysis that reads one of its report’s analysis data
(data, see The ARD spec) is
written with that data’s conditions: the where of each row
it is made from, joined into one WhereClause on the ADaM dataset, and
the population of the data it starts from. What a WhereClause cannot say
– subjects, derive, code … – is
listed by tfl_ars_unmapped(), as above.
adata <- df(output_id = "T-14-3-2",
data_id = c("adae_teae", "adae_ser"),
from = c("ADAE", "adae_teae"),
population_id = c("SAF", NA),
where = c('TRTEMFL == "Y"', 'AESER == "Y"'))
analyses <- df(output_id = "T-14-3-2", analysis_id = "SER",
method = "hierarchical", data = "adae_ser", by = "TRT01A",
variables = "AEBODSYS | AEDECOD",
purpose = "PRIMARY OUTCOME MEASURE")
sheets <- unclass(ard_spec)[c("study", "datasets", "populations")]
ard_spec2 <- tfl_ard_spec(c(sheets,
list(analysis_data = adata, analyses = analyses)))
ars2 <- tfl_ars(ard_spec2)
tfl_check_ars(ars2)
#> [1] part field problem
#> <0 rows> (or 0-length row.names)An output that prints another’s analyses
A figure that prints a table’s median and hazard ratio has no
analyses of its own. tfl_ars(references = ) names such
outputs – output_id, source (the output whose
analyses it prints) and analysis_id – and writes each as an
ARS Output whose list-of-contents item points at the
source’s analyses, which are not written twice. One whose source the ARS
does not have is listed by tfl_ars_unmapped().
Running it with siera
profile = "siera" writes ARS that siera can run – still
valid CDISC ARS, with an R code template per method.
tfl_ars_ard() runs it (siera writes and runs one programme
per output) and gives its ARD, a round trip to compare with the ARD
tflspec builds:
ars_s <- tfl_ars(ard_spec, profile = "siera")
ard <- tfl_ars_ard(ars_s, adam) # adam: a named list of datasets, or a folderAn analysis siera has no template for (a test other than chi-square,
missing, mean_ci, custom …) is
left out and listed by tfl_ars_unmapped().
Reading anyone’s ARS
tfl_read_ars_json() reads an ARS JSON – one written
elsewhere, by another tool – and tfl_ars_to_specs() turns
it into specs: $ard, $report (and with
table = TRUE, $table: the groupings’ levels).
ARS has no ADaM paths, so datasets$path is filled in before
the ARD spec runs; what has no place in the specs is in
attr(, "unmapped").
sp <- tfl_ars_to_specs(tfl_read_ars_json(json))
sp$ard$analyses[, c("output_id", "analysis_id", "method", "by", "variables")]
#> output_id analysis_id method by variables
#> 1 T-14-1-1 AGE continuous TRT01A AGE
#> 2 T-14-1-1 SEX categorical TRT01A SEX
#> 3 T-14-3-1 TEAE hierarchical TRT01A AEBODSYS | AEDECOD
sp$report$titles[, c("output_id", "line", "center")]
#> output_id line center
#> 1 T-14-1-1 1 Demographic Characteristics
#> 2 T-14-3-1 1 Treatment-Emergent Adverse EventsThere is no way back from an ARD alone (it has no data paths, populations or conditions); an existing study’s analyses come in through ARS.