Exporting ARDs as Dataset-JSON

library(siera)

The ARD that a siera script produces is an R data frame. That is perfect for working in R, but sooner or later you will want to hand your results to someone (or something) else: a QC programmer working in SAS, a results repository, a reviewer. This vignette shows how to have siera write each ARD to a CDISC Dataset-JSON file as well, in a form that is ready to exchange.

What is Dataset-JSON, and why should you care?

Dataset-JSON is CDISC’s JSON-based format for exchanging tabular clinical data. A Dataset-JSON file carries the data rows together with the metadata that describes them: each column’s name, label and data type, plus a name and label for the dataset itself.

It matters because it is the industry’s leading candidate to replace SAS version 5 transport (XPT) files. The FDA, together with CDISC and PHUSE, has piloted Dataset-JSON for regulatory submissions. Compared with XPT it has no 8-character variable name or 200-character value limits, it is an open, text-based format, and it is easy to read from R, SAS, Python or any other language.

siera writes Dataset-JSON v1.1 files.

Turning it on

Dataset-JSON export is controlled by one argument to readARS(): output_format. It defaults to "none" (generate the ARD scripts only). Set it to "datasetjson" and every generated ARD_<OutputId>.R script gets an extra section at the end that writes the ARD to a Dataset-JSON file when you run it.

# ARS metadata and the folder of ADaM datasets shipped with siera
ARS_path  <- ARS_example("exampleARS_4.json")
adam_path <- dirname(ARS_example("ADSL.csv"))

# a fresh folder for the generated script (and, later, the Dataset-JSON file)
output_path <- file.path(tempdir(), "siera-datasetjson")
dir.create(output_path, showWarnings = FALSE)

# generate the ARD script for output Out_01, with Dataset-JSON export switched on
readARS(
  ARS_path,
  output_path,
  adam_path,
  spec_output   = "Out_01",
  output_format = "datasetjson"
)

list.files(output_path)
#> [1] "ARD_Out_01.R"

The value must be spelled out in full: output_format = "dataset" is an error rather than a guess.

What the export section looks like

The export runs when you run the generated script, not when readARS() runs. That is because siera only writes the code: the ARD does not exist until the script has been run against your ADaM data, and its columns depend on what the data contains. So the export section looks at the finished ARD object and works out the column metadata from it. Here is how it starts:

script <- readLines(file.path(output_path, "ARD_Out_01.R"))
start  <- grep("Export ARD as CDISC Dataset-JSON", script)
cat(script[start:(start + 6)], sep = "\n")
#> # Export ARD as CDISC Dataset-JSON ----
#> if (requireNamespace("datasetjson", quietly = TRUE)) {
#>   .ds_df <- ARD
#> 
#>   # Dataset-JSON columns must be atomic; flatten {cards} list-columns.
#>   for (.nm in names(.ds_df)) {
#>     if (is.list(.ds_df[[.nm]])) {

You need the datasetjson package where the script runs

The file is written with the datasetjson package. siera does not require it, so it must be installed in the R session that runs the generated script (which may be a different machine from the one that ran readARS()):

install.packages("datasetjson")

If datasetjson is not installed, the script still produces the ARD object as usual. It simply prints a message that the Dataset-JSON export was skipped, instead of stopping with an error.

Running the script and finding the file

Run the generated script as you normally would. The Dataset-JSON file is written to the folder you gave readARS() as output_path, beside the script, and is named after the output: ARD_<OutputId>.json.

source(file.path(output_path, "ARD_Out_01.R"))

list.files(output_path)
#> [1] "ARD_Out_01.json" "ARD_Out_01.R"

Reading it back

Use datasetjson::read_dataset_json() to read the file back into R. You get the same rows and columns as the ARD object:

ard_json <- datasetjson::read_dataset_json(
  file.path(output_path, "ARD_Out_01.json")
)

dim(ard_json)
#> [1] 252  19
dim(ARD)
#> [1] 252  19

head(ard_json[, c("AnalysisId", "operationid", "group1_level", "res", "disp")])
#>   AnalysisId operationid group1_level         res    disp
#> 1      An_01 Mth_01_01_n            1  84.0000000  (N=84)
#> 2      An_01 Mth_01_01_n            2  84.0000000  (N=84)
#> 3      An_01 Mth_01_01_n            3  86.0000000  (N=86)
#> 4      An_02 Mth_01_01_n         <NA> 254.0000000 (N=254)
#> 5      An_03 Mth_02_01_n            1  50.0000000      50
#> 6      An_03 Mth_02_02_%            1   0.5952381  (59.5)

The column metadata travels with the data. Read the file with jsonlite and look at its columns section to see each column’s name, label and data type:

meta <- jsonlite::fromJSON(file.path(output_path, "ARD_Out_01.json"))$columns
head(meta[, c("name", "label", "dataType")], 10)
#>              name                   label dataType
#> 1          group1        Group 1 Variable   string
#> 2    group1_level           Group 1 Value   string
#> 3        variable       Analysis Variable   string
#> 4  variable_level Analysis Variable Level   string
#> 5         context       Statistic Context   string
#> 6       stat_name          Statistic Name   string
#> 7      stat_label         Statistic Label   string
#> 8            stat         Statistic Value    float
#> 9         warning                 Warning   string
#> 10          error                   Error   string

How columns are prepared

Two things happen to the ARD on its way into Dataset-JSON. Neither changes the ARD object in your R session; only the exported copy is affected.

List-columns are flattened. Every Dataset-JSON column must hold a single value per row. A few columns produced by cards are list-columns, where each cell can hold an R object. These are converted:

Variable labels are filled in. Each column needs a label. siera chooses one as follows:

  1. A label you supplied yourself through column_labels (see below).
  2. A built-in label for siera’s and cards’ standard columns, e.g. AnalysisId is “Analysis Identifier”, stat is “Statistic Value” and disp is “Formatted Result”.
  3. A generated label for the grouping columns, e.g. group1_groupingId is “Group 1 Grouping Identifier” and group2_level is “Group 2 Value”.
  4. Otherwise, the column name itself. This is what happens to columns named after ADaM variables, which siera cannot know in advance.

Using your own labels

Your submission conventions may call for different wording, or you may want proper labels for ADaM-named columns. Pass a named character vector to column_labels: the names are ARD column names and the values are the labels you want. Your labels take priority over the built-in ones, so you can reword those too.

readARS(
  ARS_path,
  output_path,
  adam_path,
  spec_output   = "Out_01",
  output_format = "datasetjson",
  column_labels = c(
    group1_level = "Actual Treatment",
    AnalysisId   = "Analysis ID"
  )
)
source(file.path(output_path, "ARD_Out_01.R"))

meta <- jsonlite::fromJSON(file.path(output_path, "ARD_Out_01.json"))$columns
meta[meta$name %in% c("group1_level", "AnalysisId", "group1_groupingId"),
     c("name", "label")]
#>                 name                       label
#> 2       group1_level            Actual Treatment
#> 12        AnalysisId                 Analysis ID
#> 15 group1_groupingId Group 1 Grouping Identifier

A few things to know about column_labels:

Where next?