Skip to contents

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:

  1. 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;
  2. finds the k nearest compatible neighbors of the anchor in a standardized, principal-component (PCA) profile space (the default is k = 5);
  3. 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
  4. 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:

  1. compute a small number of aggregate statistics from the source data, with every per-subject contribution clipped into a publicly declared range;
  2. add calibrated random noise to each aggregate, with the noise scale derived from how much one subject could have moved that number; and
  3. 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:

Pr[M(D)S]eεPr[M(D)S]+δ\Pr[M(D) \in S] \;\le\; e^{\varepsilon} \, \Pr[M(D') \in S] \;+\; \delta

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       TRUE

The 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 accounting

The 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:

f=dεNf \;=\; \frac{d}{\varepsilon N}

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

  1. 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.

  2. 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.