--- title: "Getting Started with synadam" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting Started with synadam} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Introduction `synadam` generates synthetic ADaM (Analysis Data Model) datasets from real clinical trial data, removing identifiable patient information while preserving the structure of the original datasets. **Important:** The synthetic data produced by `synadam` is **not realistic**. Values are generated to match the structural characteristics and ranges of the original data but do not preserve statistical distributions. This package is designed for use cases where exact values do not matter, such as: - Testing the generation of clinical tables. - Validating data processing pipelines. - Developing and debugging analysis code without access to real patient data. ## High-level design `synadam` uses a two-phase "glimpse-then-simulate" approach: 1. **Glimpse**: Extract statistical summaries from real data (ranges, unique values, NA patterns). 2. **Simulate**: Generate synthetic data from those summaries. This provides a clear, traceable interface between real and synthetic data, allowing users and reviewers to inspect exactly what information is retained. ```{r, echo=FALSE, out.width="100%", fig.align="center"} if (file.exists("images/synadam_overview.png")) { knitr::include_graphics("images/synadam_overview.png") } ``` ## Supported dataset types | Type | Description | Example | |------|-------------|---------| | **ADSL** | Subject-level data (one row per subject) | Demographics, treatment assignments | | **BDS** | Basic Data Structure (longitudinal) | Lab results (ADLB), vitals (ADVS) | | **OCCDS** | Occurrence Data Structure (event-based) | Adverse events (ADAE), concomitant meds | | **TTE** | Time-to-Event (survival analysis) | Overall survival (ADTTE) | ## End-to-end example This example uses sample data inspired by the [PHUSE CDISC Pilot Study](https://github.com/phuse-org/phuse-scripts), included in `inst/extdata/` for demonstration. ```{r setup} library(synadam) ``` ```{r prepare-sas-files, include=FALSE} # Convert sample CSVs to SAS format in a temp directory for the demo temp_adam_dir <- file.path(tempdir(), "adam_data") dir.create(temp_adam_dir, showWarnings = FALSE) temp_output_dir <- file.path(tempdir(), "syn_output") adsl <- read.csv( system.file("extdata", "adsl.csv", package = "synadam"), stringsAsFactors = FALSE ) adlb <- read.csv( system.file("extdata", "adlb.csv", package = "synadam"), stringsAsFactors = FALSE ) adae <- read.csv( system.file("extdata", "adae.csv", package = "synadam"), stringsAsFactors = FALSE ) # Convert date columns adsl$TRTSDT <- as.Date(adsl$TRTSDT) adlb$ADT <- as.Date(adlb$ADT) adae$ASTDT <- as.Date(adae$ASTDT) adae$AENDT <- as.Date(adae$AENDT) haven::write_sas(adsl, file.path(temp_adam_dir, "adsl.sas7bdat")) haven::write_sas(adlb, file.path(temp_adam_dir, "adlb.sas7bdat")) haven::write_sas(adae, file.path(temp_adam_dir, "adae.sas7bdat")) ``` ### Step 1: Auto-generate a configuration file Point `generate_study_config()` at a directory containing your `.sas7bdat` files. It scans the files, infers dataset types and column roles using CDISC conventions, and writes a draft YAML configuration: ```{r generate-config} yaml_path <- synadam::generate_study_config( adam_dir = temp_adam_dir, output_dir = temp_output_dir, seed = 42 ) ``` The generated YAML uses heuristics to classify columns into their roles. Fields with weaker signal are tagged with inline `# REVIEW` comments so you know what to verify: ```{r show-config} cat(readLines(yaml_path), sep = "\n") ``` ### Step 2: Review and edit the configuration Before running the simulation, review the generated YAML: - Verify `dataset_type` is correct for each dataset. - Check that `treatment_cols`, `flag_cols`, and `ordered_col_sets` are appropriate. - Remove any `flag_cols` you don't need to preserve. - Add any `ordered_col_sets` not auto-detected (e.g., `AEBODSYS`/`AEDECOD`). ### Step 3: Simulate the study Once satisfied with the configuration, generate all synthetic datasets: ```{r simulate-study} synadam::simulate_study(config_path = yaml_path) ``` Synthetic datasets are saved as individual `.rds` files in the output directory: ```{r read-results} list.files(temp_output_dir, pattern = "\\.rds$") syn_adsl <- readRDS(file.path(temp_output_dir, "syn_adsl.rds")) head(syn_adsl) # Each dataset carries a version attribute for traceability attr(syn_adsl, "synadam_version") ``` ## Writing the YAML configuration manually If you prefer full control (or need to fine-tune the auto-generated draft), author the YAML by hand. The schema supports all four dataset types: ```yaml output_dir: "/path/to/output_directory" seed: 42 datasets: adsl: dataset_type: "adsl" path: "/path/to/adsl.sas7bdat" id_cols: [USUBJID, SUBJID] treatment_cols: [TRT01A, TRT01AN] flag_cols: [SAFFL, ITTFL, EFFFL] # Optional ordered_col_sets: [[REGION1, REGION1N]] # Optional adlb: dataset_type: "bds" path: "/path/to/adlb.sas7bdat" id_cols: [USUBJID] param_cols: [PARAM, PARAMCD] visit_cols: [AVISIT, AVISITN] # Optional flag_cols: [ANL01FL] # Optional adae: dataset_type: "occds" path: "/path/to/adae.sas7bdat" id_cols: [USUBJID] seq_col: AESEQ ordered_col_sets: [[AEBODSYS, AEDECOD]] # Optional adtte: dataset_type: "tte" path: "/path/to/adtte.sas7bdat" param_cols: [PARAM, PARAMCD] censor_cols: [CNSR, EVNTDESC, CNSDTDSC] # Optional ``` **Note**: Optional fields can be set to `[]` or omitted entirely. ## Granular control: glimpse and simulate individually For more control, call the `glimpse_*()` and `simulate_*()` functions directly: ```{r individual-adsl} adsl_data <- synadam::read_sas7bdat(file.path(temp_adam_dir, "adsl.sas7bdat")) # Glimpse: extract structural summary adsl_summary <- synadam::glimpse_adsl( adsl = adsl_data, id_cols = c("USUBJID", "SUBJID"), treatment_cols = c("TRT01A", "TRT01AN"), flag_cols = c("SAFFL", "ITTFL", "EFFFL"), ordered_col_sets = list(c("REGION1", "REGION1N")), seed = 42 ) # Simulate: generate synthetic data syn_adsl <- synadam::simulate_adsl(adsl_summary, seed = 42) head(syn_adsl) ``` For BDS, OCCDS, and TTE datasets: - `glimpse_bds()` / `simulate_bds()` — longitudinal data (ADLB, ADVS) - `glimpse_occds()` / `simulate_occds()` — occurrence data (ADAE, ADCM) - `glimpse_tte()` / `simulate_tte()` — time-to-event data (ADTTE) See `?glimpse_bds`, `?glimpse_occds`, `?glimpse_tte` for full documentation. ## Splitting glimpse from simulate `simulate_study()` runs both phases in one call. To run them separately — for example, to glimpse on a machine that has access to the real `.sas7bdat` files and then simulate elsewhere — use `glimpse_study()` and `simulate_study_from_summary()`. ```{r split-glimpse-simulate} study_summary_path <- file.path(temp_output_dir, "study_summary.rds") # Phase 1: glimpse only. Requires the real SAS files. synadam::glimpse_study( config_path = yaml_path, study_summary_path = study_summary_path ) # The study summary is one .rds with all summaries plus seed and version. str(readRDS(study_summary_path), max.level = 2) # Phase 2: simulate from the study summary. Does not need the SAS files. split_output_dir <- file.path(tempdir(), "syn_output_split") synadam::simulate_study_from_summary( study_summary_path = study_summary_path, output_dir = split_output_dir ) list.files(split_output_dir, pattern = "\\.rds$") ``` Pass `seed` to `simulate_study_from_summary()` to draw a different synthetic replicate from the same summaries; omit it to reuse the seed stored in the study summary. ## Privacy protection `synadam` implements multiple privacy mechanisms: 1. **Structure, not values**: Preserves column names, unique values, and ranges, but not distributions or clinical patterns. 2. **Singleton removal**: Unique values or combinations appearing only once are excluded and replaced with more common values. 3. **NA noise**: 5% of NA positions are randomly flipped to prevent exact pattern reproduction. ## Limitations Synthetic data from `synadam` is **not suitable** for: - **Realistic simulations** — does not preserve statistical distributions. - **Statistical analysis** — results will not reflect real clinical findings. - **Regulatory submissions** — unless explicitly approved. ## Disclaimer ⚠️ Users are responsible for correctly specifying all column categories in the configuration. Incorrect configuration can result in private data leaking into synthetic data. - **ID columns** (`id_cols`): Must include all subject identifiers. - **Ordered column sets** (`ordered_col_sets`): Should include the minimum sets of related columns required for your use case. **Always review your configuration carefully and validate that synthetic data does not contain identifiable information before sharing outside GxP environments.**