Scope
synpmx generates synthetic pharmacometric (PMX) datasets
— dosing and measurement event tables used for a population
pharmacokinetic (PK) or pharmacodynamic analysis. It offers three
families of method that protect patients in fundamentally different
ways. This vignette explains what each one actually is, what
differential privacy does and does not promise, how to choose between
methods and if a private mode is chosen, how to choose an epsilon.
The companions are The synpmx data generation algorithms (the generation modes) and Evaluating AVATAR on public data (AVATAR measured on public data).
The three method families
They differ on one axis above all: what of the real data reaches the output. AVATAR sends real trajectories, blended. The DP engines send noised aggregates. Public-model simulation sends nothing, because it never opens the file.
AVATAR blending: synthetic data built from real subjects
The default method in this package is
synpmx_avatar(), AVATAR-style blending [1, 2]. “AVATAR” is
a method name rather than an initialism: it comes from the
patient-centric avatarization literature, in which each
synthetic record (“avatar”) is constructed from the local neighborhood
of real records rather than from a fitted parametric model. The original
method is due to Guillaudeux and colleagues [2]; Destere and colleagues
benchmark a modified AVATAR for population PK [1]. This package
implements an AVATAR-inspired adaptation for longitudinal event
tables, not published AVATAR software.
Mechanically, for each synthetic subject it:
- samples a real source subject as an anchor and keeps that subject’s event skeleton — dosing rows, observation times, missing-value pattern — so the generated subject is structurally a real trial subject;
- finds the k nearest compatible neighbors of the
anchor in a standardized, principal-component (PCA) profile space (the
default is
k = 5); - fills covariates and endpoint trajectories with a randomized, distance-weighted blend of those donors, with no single donor allowed more than 50% of the weight; and
- adds subject-level and within-trajectory random noise.
The output looks like trial data because it is assembled from trial
data. That is the source of both its utility and its risk: it works at
any cohort size, preserves real covariate correlations and real
trajectory shapes for free, needs no elicited model or priors — and
makes no formal privacy guarantee. Its safety rests on
governance: the data stays inside a trusted, access-controlled
environment. This is the same footing as Novartis’s
synadam, an ADaM (Analysis Data Model) synthetic-data
package that resamples each column marginally from the real data with no
privacy accounting at all.
Differential privacy: a bound on what any one subject can change
The second family is the differentially private (DP)
engines, synpmx_calibrated() and
synpmx_empirical(). These never copy or blend a subject’s
data into the output. Instead they:
- compute a small number of aggregate statistics from the source data, with every per-subject contribution clipped into a publicly declared range;
- add calibrated random noise to each aggregate, with the noise scale derived from how much one subject could have moved that number; and
- generate the synthetic dataset from the noised aggregates and a public structural model.
Only the noised aggregates ever touch the generated data, so the release carries a mathematical guarantee.
Public-model simulation: synthetic data that never reads the real data
The third family is synpmx_prior(), and
it does not answer the privacy question so much as step around it. Give
it a public structural model and a trial design and it simulates
subjects from those alone. It never opens the source data — its
arguments do not even include a dataset.
That buys a property neither other family can have. AVATAR has no formal guarantee; the DP engines have a proven one. Public-model simulation needs neither, because nothing about any real patient is ever read. There is no disclosure to bound, no budget to spend, and epsilon simply does not apply.
But that property belongs to the model, not to the function —
and this is the one thing to get right. Every parameter must be
fixed before anyone looks at the source data: allometric
scaling from preclinical work, a published model for the compound class,
the reasoning that set the starting dose. If instead you fit a model to
the real study and feed those estimates to synpmx_prior(),
the real data reaches the output through the parameters, and the
“nothing was read” claim is simply false. The generated rows would carry
real information about real patients while looking, to every downstream
reader, like pure simulation.
synpmx_prior() cannot detect this. It sees a model
object and has no way to know where the numbers in it came from.
pmx_structural_model() and pmx_trial_design()
each take a source argument to record that provenance in
writing, but it is a statement by the author, not a check by the
package. The discipline is yours to keep.
This is the same failure mode as a data-derived clipping range in the DP engines — an input that quietly smuggles the data past the accounting — and it deserves the same suspicion. If a number in your model was chosen because it made the synthetic data look more like the real data, it came from the data.
The cost is the exact mirror of the benefit. Everything in the output is asserted rather than learned, so the fidelity of the result is precisely the fidelity of the model, design, and priors you supplied. It cannot discover a covariate relationship you did not put in, and it will reproduce a wrong assumption faithfully and without complaint. Its natural uses are exercising pipeline code before any data exists, and cases where the real data cannot be touched at all.
Producing those public inputs without reading data is its own skill; see the model elicitation and data elicitation articles.
What differential privacy actually is
Differential privacy is a property of the release procedure, not of the released dataset. You cannot inspect a table and check whether it is differentially private; you can only check the mechanism that produced it.
A randomized mechanism M is
(epsilon, delta)-differentially private if, for every pair
of neighboring datasets D and
D' — identical except that one complete subject is added or
removed — and every set of possible outputs S:
Read it as a promise made to one patient: whatever the analyst concludes from this release, they would have concluded almost the same thing had you never enrolled. The bound holds against an adversary with unlimited computing power and arbitrary side information, including one who already knows every other subject in the study.
Making that true requires two ingredients, both visible in the package:
- Sensitivity — the most that adding or removing one subject can move the released quantity. It exists only because per-subject values are clipped into a range declared without looking at the data so that an extreme value does not have a large impact on the synthetic data. A data-derived range is itself a leak, and the accounting will not catch it.
-
A noise scale tied to that sensitivity. The engines
use the Laplace mechanism: noise of scale
sensitivity / epsilon. Smaller epsilon means a stronger promise and more noise.
The privacy background article works the arithmetic through with examples.
The three families are fundamentally different
It is tempting to rank the methods as “less private” and “more private” but the difference is more substantial.
| AVATAR blending | Differentially private engines | Public-model simulation | |
|---|---|---|---|
| Function | synpmx_avatar() |
synpmx_calibrated(),
synpmx_empirical()
|
synpmx_prior() |
| Reads the source data | Yes, whole trajectories | Yes, as clipped aggregates | Never — and neither may the model it is given |
| What produces the output | Real subject trajectories, blended and perturbed | Noised aggregate statistics plus a public structural model | A public structural model and trial design alone |
| Privacy claim | None | Proven (epsilon, delta) bound on one subject’s
influence |
Nothing is disclosed |
| Holds against a determined adversary | Not established | Yes, by construction | Yes, there is nothing to attack |
| Rests on | Governance and access control | Mathematics, plus correct declared ranges | Discipline: that every parameter was fixed before anyone looked at the data |
| Utility at small N | Good | Degrades sharply below a few hundred | Independent of N — it depends on your model, not your cohort |
| What you must supply | Nothing but the source data | A public structural model, priors, and clipping ranges — all worked out without looking at the data | A public structural model and a trial design |
A caveat about AVATAR is that a resampled subject
trajectory is much closer to a fingerprint for a
patient than a randomly chosen covariate. A trajectory contains
particular sampling times, missed visits, noise pattern, and curve shape
can be nearly unique. Blending several donors and adding noise mitigates
this, but not formally, and pushing the noise high enough to defeat a
nearest-neighbor linkage attack would destroy the same signal a DP
mechanism would have destroyed — without the accounting to prove it.
AVATAR therefore leans on the governance context more heavily than
column-wise resampling like synadam does. This package has
undergone no attack-based privacy validation.
What each family asks of you
The quickest way to feel the difference is to ask what you have to supply. Ordered below, the amount you must assert falls and the amount taken from the data rises.
1. Public-model simulation. You specify everything: the structural model, every typical parameter value, the dose levels, and the full sampling schedule. All of it has to come from somewhere that is not this study — allometric scaling from preclinical work, a published model for the compound class, the reasoning behind the starting dose. You never look at the data; the function does not even take a dataset. The most work of the three, and in exchange the privacy question never arises.
2. Differential privacy. You still assert the model,
the design, and the prior ranges, and then buy a small correction toward
reality with epsilon. For synpmx_calibrated() exactly
two numbers leave the data: a bounded multiplicative
correction to the model’s magnitude, and a noised subject count.
Everything else — curve shape, between-subject variability, residual
error, covariate relationships — stays asserted from the public model
and costs nothing. synpmx_empirical() sits further along
the same axis: it buys many more numbers and asserts less, so the same
epsilon splits many more ways, which is why it needs a far larger cohort
to be worth it.
3. AVATAR. You name the columns — which one is the subject ID, which is time, which is the measurement, which is the dose — and it works out the rest from the data itself. No model, no design, no priors, no budget. Least work by a wide margin, and for the same reason the least to stand on: there is no asserted model to point at and no formal guarantee either.
The pattern is worth stating plainly, because it is not a coincidence: the work you did not do is the guarantee you do not get. Each step down this list trades an assertion you would have had to defend for a fact taken from real patients, and the privacy claim weakens by exactly that much.
For all three run end to end on theo_md, with code and
output, see The
synpmx data generation algorithms.
The decision rule
Two questions, in order.
First: does the output need to reflect the real data at all?
-
No — you are exercising pipeline code, the study
has not read out yet, or the data cannot be touched → use
public-model simulation (
synpmx_prior()). Nothing is read, so the privacy question never arises. You are trading away all fidelity to the real cohort in exchange for having no disclosure to argue about. - Yes — the synthetic data has to resemble these patients → ask the second question.
Second: does the generated data reach anyone the source data could not?
-
No — the same organization, under the same access
controls and confidentiality obligations → use AVATAR
(
synpmx_avatar()). - Yes — shared with a partner or vendor, published, or moved to a system outside those obligations → use a DP engine. A formal guarantee is the only thing that survives a determined adversary.
Differential privacy is more complex precisely because it defends against someone who wants to break it. Use it when the output will be handed to strangers; skip it when the synthetic data does not reach anyone outside the trusted organization.
Note that the first question is about utility, not privacy. That is the point: public-model simulation is the only mode whose privacy story is settled before you start, so the only thing left to weigh is whether a model-derived cohort is good enough for what you are doing.
The differentially private engines
Both make a subject-level guarantee: neighboring datasets differing by one complete subject produce nearly indistinguishable output, so no one person’s participation can be inferred from the release.
-
synpmx_calibrated()(structural correction). Asserts curve shape from a public structural model and privately calibrates only the exposure magnitude by a small correction factor. Because it releases very few quantities, it remains viable at small cohorts. This is the recommended DP path for early-phase studies. -
synpmx_empirical()(dense grid). Releases a larger set of noised population summaries. Retained for large pooled corpora where its cost is affordable.
Both are built on Laplace releases, which spend no delta, so the
realized accounting reports delta = 0 — a pure epsilon
guarantee — even though the requested delta is carried as
slack in the contract. Both fail closed when the validated OpenDP
backend is unavailable, and neither ever substitutes ordinary random
noise for a calibrated mechanism:
dp_backend_status()
#> backend available version production
#> 1 OpenDP TRUE 0.15.1 TRUEThe unaudited status is enforced, not just documented
Both engines are complete and tested, but not under active development, carry known open findings, and have not been independently privacy-audited. Treat them as a principled demonstration of the privacy/utility tradeoff, not as a production release mechanism — a real regulated release needs specialist review.
That distinction used to live in documentation alone, which made the
risky path exactly as easy to call as synpmx_avatar(). Both
functions now refuse to run until
synpmx_enable_dp_engines() has been called once in the
current session:
The acknowledgment does not persist across sessions, script runs, or
CI jobs — each one must call it again. backend = "public"
calls make no DP claim already and are exempt from the gate.
Budget is spent once, per call
Both functions read the confidential data exactly once and return a synthetic dataset with the resulting release attached to it. Generating from that release again is post-processing: it reads nothing and costs nothing.
syn <- synpmx_calibrated(data, roles, model, design, priors, epsilon = 1)
syn2 <- synpmx_generate(syn, seed = 202) # free
privacy_report(syn) # the realized accountingThe distinction matters because calling
synpmx_calibrated() or synpmx_empirical() a
second time is a second release, whose budget must be
composed with the first — two calls at epsilon = 1 spend 2
in total, not 1. Use synpmx_generate() for further
datasets, or ask for several at once with n_datasets. A
repeated fit against the same data warns with the running total, but the
accounting is a governance obligation, not something the package can
enforce for you.
What the guarantee does and does not mean
- Epsilon is the one-person influence limit: smaller is stronger. It is not a re-identification probability.
- Delta is a small additive slack in the probability bound. It is not the fraction of unprotected patients.
- Differential privacy bounds the information attributable to one person’s participation. It does not establish legal anonymity, authorize release, or secure the environment.
- The guarantee is only as good as the declared clipping ranges. Ranges chosen by looking at the data break it silently.
Choosing an epsilon
Everything below applies to the differentially
private modes only. If your synthetic data stays within the
source data’s own access controls and obligations, use
synpmx_avatar() and ignore epsilon entirely.
Epsilon is the one-person influence limit: smaller means stronger privacy and more noise, larger means weaker privacy and less noise. There is no universal default; an approved value must come from governance and threat modeling, not from whichever number makes a plot look best.
For the structural-correction engine
(synpmx_calibrated()), the usable accuracy is captured by
one quantity:
where d is the number of released quantities,
N the number of subjects, and f the fraction
of the prior’s width that survives as noise. The
pmx_preflight() helper reports it before any budget is
spent:
priors <- pmx_priors(pk = pmx_prior(c(1 / 4, 4), source = "example"))
pmx_preflight(priors, epsilon = 0.5, n_subjects = 60)
#> Pre-flight: d = 2, epsilon = 0.5, N = 60 -> f = 0.067
#> quantity prior_fold f expected_fold_error
#> pk 16 0.06666667 1.203025
#>
#> Verdict: consider a smaller epsilon
#> The prior contributes almost nothing. Consider spending less epsilon rather than banking accuracy you do not need.The decision rule is not “is the error small” but “does the release
beat the prior”: when f is near or above 1 the release
conveys nothing the prior did not, and generating from public inputs
alone is strictly better.
The measured frontier
Utility degrades sharply in small cohorts, and this is a property of differential privacy, not of the implementation. Measured total fold-error on clearance, for the structural engine with a correction-factor prior:
| N | epsilon 0.25 | epsilon 1 |
|---|---|---|
| 6 | 2.3-fold | 1.7-fold |
| 20 | 1.6-fold | 1.3-fold |
| 60 | 1.4-fold | 1.1-fold |
| 300 | 1.08-fold | 1.06-fold |
From about 60 subjects upward the error is limited by estimator bias rather than by the privacy mechanism, so a smaller epsilon is the better buy: it strengthens the guarantee at no real cost in accuracy.
Epsilon and delta are governance decisions, not defaults. For
anything public facing they should be set and justified by whoever owns
the data, and recorded: every fit carries a release ledger, and
privacy_report() prints the realized accounting.
Where to read more
-
Privacy
background — how the arithmetic works:
d,f, epsilon, and the error law, with worked examples. - Mechanism-level privacy argument — the formal argument, for a reviewer.
- Feasibility by cohort size — the complete measured frontier, and why small cohorts are hard for any formal method.
-
The
synpmx data generation algorithms — the generation modes and why
AVATAR is the default. Note that it splits public-model simulation and
the two DP engines into three separate modes, where this
article groups the DP engines into one family: same functions,
counted differently.
synpmx_pca()makes no formal privacy claim and sits beside AVATAR rather than in either grouping. - The AVATAR Algorithm — the default generator in detail.
- Model elicitation and data elicitation — how to produce the public model, priors, and design that public-model simulation and the DP engines both need.
References
Destere A, Lombardi R, Labriffe M, et al. Can synthetic data overcome the privacy and fidelity bottleneck in Pharmacometrics? A comparative benchmark using a daptomycin population pharmacokinetic model. medRxiv preprint, posted June 2, 2026. doi: 10.64898/2026.05.30.26354512.
Guillaudeux M, Rousseau O, Petot J, et al. Patient-centric synthetic data generation, no reason to risk re-identification in biomedical data analysis. npj Digital Medicine. 2023;6. doi: 10.1038/s41746-023-00771-5.