Skip to contents

The measurement for donor blending, the one masking mechanism synpmx_avatar() applies to the values rather than the structure. skeleton_uniqueness() answers the structural question by counting who shares which schedule; this answers the geometric one, by asking whether each subject's nearest neighbour lies in its own dataset or the other one.

Usage

compare_pmx_proximity(
  source,
  synthetic,
  roles,
  replicates = 50L,
  seed = 1L,
  pca_variance = 0.9
)

Arguments

source

Source PMX data.

synthetic

Generated synthetic PMX data.

roles

Explicit roles from pmx_roles().

replicates

Split-half replicates used to build the null. Default 50.

seed

Seed for the subsampling and splits. The caller's RNG is left untouched.

pca_variance

Variance retained when both datasets are projected into a common profile space. Default 0.90, matching synpmx_avatar().

Value

A one-row pmx_proximity data frame: adversarial_accuracy, null_lower / null_upper (the central 95% of the split-half null), verdict, n_compared (patients per side, the same on both arms and in the null), n_null_replicates, and the 5th-percentile nearest-neighbour distances synthetic_to_source_q05 and source_to_source_q05.

Details

The reported statistic is a nearest-neighbour adversarial accuracy in \([0, 1]\):

  • near 0.5 — a synthetic subject is no more like a real subject than one real subject is like another. This is the target.

  • toward 0 — synthetic subjects sit closer to real subjects than to each other. That is memorisation, and it is the privacy failure.

  • toward 1 — the two sets have separated. Privacy is fine and utility is not.

Raw distance to the closest real record is deliberately not the headline. It has no natural scale, and measured against zero it mostly tracks cohort size — the nearest of N points gets closer as N grows, so a larger source would score worse while blending across more donors actually makes it safer. The quantiles are still returned for context, alongside the real-to-real quantiles they should be read against.

The null interval comes from running the identical statistic on two halves of the source cohort, so every small-sample artefact is present in the null and the observed value alike and cancels. At the cohort sizes pharmacometrics works with, that interval is wide: this will catch a blatant leak, not a subtle one. Treat a value inside the interval as "nothing detected", never as "nothing there".

Marked "restricted_not_releasable": it reads the source.

Examples

data <- pmx_simulated_fixture(40)
roles <- pmx_roles(
  id = "ID", time = "TIME", dv = "DV", amt = "AMT", evid = "EVID",
  cmt = "CMT", dvid = "DVID", covariates = "WT"
)
synthetic <- suppressWarnings(synpmx_avatar(data, roles, seed = 1))
#> synpmx_avatar(): dropped 9 undeclared column(s): NTIME, TAD, OCC, RATE, MDV, CENS, LIMIT, AGE, SEX.
#>   Declare a column in `keep` to carry it through verbatim.
compare_pmx_proximity(data, synthetic, roles, replicates = 10)
#> PMX nearest-neighbour proximity check
#> 
#>   Question: is a synthetic patient closer to a real patient than real
#>     patients are to each other?
#>   Measured 0.800, on a scale where 0.5 means 'no closer' and is the target;
#>     0 would mean every synthetic patient is glued to a real one.
#>   Expected 0.356 to 0.666 if nothing were wrong. That interval is not
#>     assumed -- it is the same statistic run 10 times on two halves of the
#>     real cohort, 20 patients per half, which is also how many synthetic
#>     patients were compared.
#>   Verdict: Too far apart. The two sets have separated, so a classifier
#>     could tell them apart. That is a utility problem, not a privacy one.
#>   For context, distance to the nearest neighbour (5th percentile, so the
#>     closest pairs): synthetic-to-real 0.588 versus real-to-real 0.106.
#>     These are only comparable to each other; the units are PCA profile
#>     space.
#> 
#> Source-derived; not releasable unless separately public or privately
#> budgeted.