flexsynth

R-CMD-check Lifecycle: experimental License: MIT

Flexible synthetic data for nested, longitudinal and linked multi-table data.

flexsynth generates utility-oriented synthetic data for supported flat, nested, longitudinal, and tree-linked structures, working natively in long format — no pivoting nested or longitudinal data to wide. It has first-class support for tree-linked multi-table data (e.g. patients → admissions → procedures / labs / meds), with referential integrity preserved by construction.

It ships two engines behind one interface:

Synthetic data is not anonymisation, and Track A output must never be described as differentially private.

Features

Installation

# install.packages("remotes")
remotes::install_github("lauyeehow1986-hub/Flexsynth")

The package installs with base-R dependencies. The default method = "cart" requires the suggested rpart package; ranger / partykit unlock additional tree methods, and data.table unlocks the row-binding fast-path. Use method = "sample" when only base R is available.

Quick start

Single nested / longitudinal table

library(flexsynth)

df <- data.frame(
  id    = rep(1:20, each = 2),
  visit = rep(1:2, times = 20),
  age   = rep(round(rnorm(20, 60, 8)), each = 2),
  sbp   = round(rnorm(40, 130, 15))
)

res <- synth(df, structure = ~ id / visit, method = "cart", seed = 1)
head(as.data.frame(res))

Multiple linked tables, synthesised jointly

patients <- data.frame(id = 1:50, sex = sample(c("F", "M"), 50, TRUE))
adm <- do.call(rbind, lapply(patients$id, function(pid) {
  n <- 1 + rpois(1, 0.6)
  data.frame(id = pid, admission_id = seq_len(n), los = 1L + rpois(n, 3))
}))

res <- synth_linked(
  tables     = list(patients = patients, admissions = adm),
  structures = list(patients   = ~ id,
                    admissions = ~ id / admission_id),
  keys       = list(patients   = "id",
                    admissions = c("id", "admission_id")),
  seed = 1
)
syn <- as.list(res)
check_linkage(syn, keys = list(patients = "id",
                               admissions = c("id", "admission_id")))

Diagnostics and disclosure risk

syn <- as.data.frame(synth(df, structure = ~ id / visit, seed = 1))
analysis_vars <- c("age", "sbp")             # exclude generated structure keys
d <- diagnose(real = df, syn = syn, vars = analysis_vars)
plot(d)                                  # overlaid marginals
disclosure_risk(real = df, syn = syn, quasi = analysis_vars)

Pooled inference from synthetic data

# Analyse all m synthetic sets with a published fully-synthetic pooling rule.
# A single set analysed naively generally under-states synthesis uncertainty.
res <- synth(df, structure = ~ id / visit, m = 10, seed = 1)
synth_glm(res, sbp ~ age)                 # pooled linear model
# any estimator works via pool_synth(res, function(d) <fit returning coef/vcov>)

Opt into differential privacy (Track B)

dp <- dp_control(epsilon = 1, delta = 1e-6, mechanism = "gaussian",
                 bounds = list(visit = c(1, 2), age = c(18, 100), sbp = c(60, 240)))
dp_res <- synth(df, structure = ~ id, privacy = dp, seed = 1)
dp_res$privacy                           # the (ε, δ) accounting record

Track B supports three release shapes:

dp_control() also exposes privately learned domains, cross-table conditioning, higher-order transitions, adaptive marginal selection, Private-PGM reconciliation, and AIM-style models. These controls trade statistical fidelity, cell sparsity, runtime, and privacy budget; they are not universally beneficial.

A governed DP release requires more than setting epsilon: define the privacy unit and public domain assumptions, justify contribution caps, retain the accounting record, review utility at the intended analysis grain, and obtain independent privacy review. See vignette("differential-privacy") for the supported combinations, accounting model, and limitations.

Learn more

Data & privacy

Bundled example datasets are fully synthetic cardiac data (see data-raw/make_toy_cardiac.R). No real patient data ships with this package, and none should ever be committed.

License

MIT © 2026 flexsynth authors