Introduction

Hospitals train AI on patient records. Census bureaus publish demographic tables. Banks share fraud patterns with researchers. In every case the goal is the same: release what the data says without revealing who said it.

The naive fix — strip names and IDs — has been broken dozens of times. In 1997, Latanya Sweeney re-identified 87 % of Massachusetts residents from a supposedly anonymized medical file using only ZIP code, birth date, and sex. Names were gone; the people were not.

Differential privacy (DP), formalized by Cynthia Dwork and colleagues around 2006, takes a different route. Instead of hiding information after the fact, it adds calibrated random noise before any answer leaves the database. The guarantee is mathematical: whether or not your record is in the dataset, the published output looks almost the same. No adversary — no matter how much side information they hold — can gain more than a tiny, controlled advantage by learning you were in the study.

Differentially private synthetic data pushes this further. Rather than answering individual queries with noise, it publishes an entirely synthetic dataset — fake rows that no real person ever lived, but whose aggregate statistics (means, histograms, correlations) are close to those of the original. Analysts run any analysis they like on the synthetic copy; the privacy budget is spent once, at generation time.

The tension is fundamental: more accuracy demands less noise; more privacy demands more noise. The parameter ε\varepsilon (epsilon) controls the trade-off. Small ε\varepsilon is strong privacy; large ε\varepsilon is more utility. There is no free lunch, and the P vs NP landscape of the underlying combinatorics makes some generation methods provably hard to compute.

Try It

The demo below shows a tiny real dataset of 20 points drawn from two clusters. The generator measures the mean and standard deviation of each coordinate, adds Laplace noise scaled to 1/ε1/\varepsilon, then draws a fresh synthetic sample from the noisy parameters.

<!-- Main layout — controls at top, canvas plot, legend, stats summary -->
<div class="controls">
  <label for="eps-slider">Privacy budget ε: <strong id="eps-val">1.0</strong></label>
  <input id="eps-slider" type="range" min="0.1" max="10" step="0.1" value="1.0">
  <button id="btn-regen" type="button">Regenerate</button>
</div>
<canvas id="plot" width="480" height="320"></canvas>
<div class="legend">
  <span class="dot real"></span> Real data
  <span class="dot synth"></span> Synthetic data
</div>
<div id="stats-box" class="stats-box"></div>
/* Demo styles — minimal layout, muted palette */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .5rem; font-size: .92rem; }
label { font-weight: 600; }
input[type=range] { flex: 1 1 120px; accent-color: #1d3557; }
button { font: 600 13px system-ui; padding: .35rem .8rem; background: #1d3557; color: #fff; border: none; border-radius: 7px; cursor: pointer; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 10px; background: #f8fafc; max-width: 100%; }
.legend { display: flex; gap: 1rem; font-size: .84rem; margin: .4rem 0; }
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; }
.dot.real { background: #1d6fa5; }
.dot.synth { background: #e05c2b; }
.stats-box { font-size: .82rem; color: #445; margin-top: .3rem; line-height: 1.6; }
/* DP synthetic data demo — real points stay hidden, synthetic points mirror noisy stats */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));

/* 20 real points in two clusters: cluster A near (2.5, 3.2), cluster B near (8.1, 7.7) */
const REAL = [
  [2.1,3.4],[2.8,2.9],[1.9,3.8],[3.2,2.6],[2.5,3.1],[2.0,4.0],[3.5,3.3],[2.3,2.8],[1.7,3.5],[3.0,2.4],
  [8.1,7.6],[7.5,8.2],[8.8,7.1],[7.2,8.5],[8.4,7.8],[9.0,7.3],[7.8,8.0],[8.3,6.9],[7.0,8.4],[8.6,7.5]
];

/* Sensitivity for mean queries over a bounded range [MIN_V, MAX_V] with N points */
const MIN_V = 0, MAX_V = 12;
const RANGE = MAX_V - MIN_V;
const N = REAL.length;
const SENSITIVITY = RANGE / N;

function laplace(scale) {
  /* Inverse CDF method for Laplace(0, scale) noise — privacy mechanism */
  const u = Math.random() - 0.5;
  return -scale * Math.sign(u) * Math.log(1 - 2 * Math.abs(u));
}

function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }

function computeStats(pts) {
  /* Compute mean and std-dev for each coordinate across all points */
  const mx = pts.reduce((s, p) => s + p[0], 0) / pts.length;
  const my = pts.reduce((s, p) => s + p[1], 0) / pts.length;
  const sx = Math.sqrt(pts.reduce((s, p) => s + (p[0]-mx)**2, 0) / pts.length);
  const sy = Math.sqrt(pts.reduce((s, p) => s + (p[1]-my)**2, 0) / pts.length);
  return { mx, my, sx, sy };
}

function generateSynth(eps) {
  /* Add Laplace noise to real stats to produce noisy parameters for synthetic generation */
  const s = computeStats(REAL);
  const noiseScale = SENSITIVITY / eps;
  const nmx = s.mx + laplace(noiseScale);
  const nmy = s.my + laplace(noiseScale);
  const nsx = clamp(s.sx + laplace(noiseScale), 0.1, RANGE);
  const nsy = clamp(s.sy + laplace(noiseScale), 0.1, RANGE);
  /* Draw N synthetic points from a Gaussian with the noisy mean and std-dev (Box-Muller) */
  const synthPts = [];
  for (let i = 0; i < N; i++) {
    const u1 = Math.random(), u2 = Math.random();
    const z1 = Math.sqrt(-2*Math.log(u1+1e-12)) * Math.cos(2*Math.PI*u2);
    const z2 = Math.sqrt(-2*Math.log(u1+1e-12)) * Math.sin(2*Math.PI*u2);
    const x = nmx + z1 * nsx;
    const y = nmy + z2 * nsy;
    synthPts.push([x, y]);
  }
  return { synthPts, noisyStats: { nmx, nmy, nsx, nsy } };
}

const canvas = document.getElementById('plot');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const PAD = 28;

function toCanvas(v) {
  return [
    PAD + (v[0] - MIN_V) / RANGE * (W - 2*PAD),
    H - PAD - (v[1] - MIN_V) / RANGE * (H - 2*PAD)
  ];
}

function drawAxes() {
  /* Draw axis lines and tick labels */
  ctx.strokeStyle = '#bcc8d4';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(PAD, PAD); ctx.lineTo(PAD, H-PAD);
  ctx.lineTo(W-PAD, H-PAD);
  ctx.stroke();
  ctx.fillStyle = '#889';
  ctx.font = '10px system-ui';
  for (let v = 0; v <= MAX_V; v += 3) {
    const [cx] = toCanvas([v, 0]);
    const [, cy] = toCanvas([0, v]);
    ctx.fillText(v, cx - 4, H-PAD+12);
    ctx.fillText(v, 2, cy + 4);
  }
}

function drawDots(pts, color, alpha) {
  ctx.globalAlpha = alpha;
  ctx.fillStyle = color;
  for (const p of pts) {
    const [x, y] = toCanvas(p);
    ctx.beginPath(); ctx.arc(x, y, 5, 0, 2*Math.PI); ctx.fill();
  }
  ctx.globalAlpha = 1;
}

let currentSynth = null;

function render() {
  const eps = parseFloat(document.getElementById('eps-slider').value);
  document.getElementById('eps-val').textContent = eps.toFixed(1);
  const { synthPts, noisyStats } = generateSynth(eps);
  currentSynth = synthPts;

  ctx.clearRect(0, 0, W, H);
  drawAxes();
  drawDots(REAL, '#1d6fa5', 0.7);
  drawDots(synthPts, '#e05c2b', 0.7);

  const rs = computeStats(REAL);
  const statsBox = document.getElementById('stats-box');
  statsBox.innerHTML =
    `<b>Real stats:</b> ${i18nFmt('mean (%mx%, %my%)', { mx: rs.mx.toFixed(2), my: rs.my.toFixed(2) })}, ${i18nFmt('std (%sx%, %sy%)', { sx: rs.sx.toFixed(2), sy: rs.sy.toFixed(2) })}<br>` +
    `<b>Noisy stats (used for synthesis):</b> ${i18nFmt('mean (%mx%, %my%)', { mx: noisyStats.nmx.toFixed(2), my: noisyStats.nmy.toFixed(2) })}, ${i18nFmt('std (%sx%, %sy%)', { sx: noisyStats.nsx.toFixed(2), sy: noisyStats.nsy.toFixed(2) })}<br>` +
    `<span style="color:#888">${i18nFmt('ε = %eps% → Laplace noise scale = %scale%', { eps: eps.toFixed(1), scale: (SENSITIVITY/eps).toFixed(3) })}</span>`;
}

document.getElementById('eps-slider').addEventListener('input', render);
document.getElementById('btn-regen').addEventListener('click', render);
render();
Result
Console

Slide epsilon toward 0.1 (strong privacy) and the synthetic cloud drifts far from the original. Slide it toward 10 (weak privacy) and the two overlap almost perfectly. The real points are never revealed — only the noisy statistics used to generate the synthetic ones.

The Real Complexity

The formal guarantee of differential privacy says: for any two datasets DD and D′D' that differ in exactly one row, and for any output SS,

Pr⁡[M(D)∈S]≤eε⋅Pr⁡[M(D′)∈S]+δ\Pr[\mathcal{M}(D) \in S] \leq e^{\varepsilon} \cdot \Pr[\mathcal{M}(D') \in S] + \delta

where M\mathcal{M} is the randomized mechanism. When δ=0\delta = 0 this is called pure DP; small δ>0\delta > 0 gives the slightly weaker but more practical approximate DP.

  • Sensitivity: the maximum change one row can cause in the statistic. For a count query over nn records, sensitivity is 1/n1/n; for a mean it can be much larger. Noise must be scaled to sensitivity divided by ε\varepsilon.
  • The Laplace mechanism: add noise drawn from Lap(0,Δ/ε)\text{Lap}(0, \Delta/\varepsilon) to each real-valued answer, where Δ\Delta is the ℓ1\ell_1-sensitivity. Simple, optimal for pure DP on numeric queries.
  • Composition: run kk queries with budget ε\varepsilon each and the total privacy loss is at most kεk\varepsilon (basic composition) or roughly εkln⁡(1/δ)\varepsilon\sqrt{k\ln(1/\delta)} (advanced composition). A fixed budget must be divided among all queries.
  • The hardness barrier: privately releasing a synthetic dataset that matches all low-degree statistics simultaneously requires sample complexity exponential in the data dimension under standard cryptographic assumptions (Ullman & Vadhan, 2011). Practical algorithms target restricted families of statistics — marginals, histograms, workload-specific queries — and accept either weaker guarantees or approximation.

This is not a bug. It reflects the same information-theoretic tension that runs through differential privacy and randomized algorithms: you cannot extract signal without paying a noise tax, and computing the optimal synthetic release is in general as hard as breaking the cryptographic primitives that underpin the hardness proof.

Where It Matters

Differentially private synthetic data is moving from theory to production in several high-stakes domains:

  • Healthcare: hospitals and biobanks generate synthetic patient cohorts for drug-trial simulations and AI model training, enabling research on rare conditions without exposing anyone's records. The NHS and several US hospital networks have run pilot programs using DP synthetic data.
  • Census and statistics: the US Census Bureau used differential privacy in the 2020 decennial census, one of the largest DP deployments in history. The synthetic micro-data it released let researchers run custom tabulations without seeing real household records.
  • AI training data: large language models and image classifiers trained on synthetic DP data can be deployed without triggering GDPR data-minimization rules, because the training set contains no real individuals.
  • Financial crime: banks share DP synthetic transaction logs with regulators and peer institutions to build joint fraud-detection models without revealing customer identities or proprietary patterns.
  • Software testing: developers seed test environments with synthetic data that behaves like production, eliminating the accidental leakage that comes from using real records in staging systems.

The common thread: share the shape of the data, not the data itself. Differential privacy makes that phrase into a theorem rather than a wish.

Conclusion

For decades, privacy was a policy — a set of rules about who could see what. Differential privacy makes it a theorem: given ε\varepsilon and δ\delta, any adversary's advantage is bounded, regardless of what they already know. Synthetic data extends that theorem to cover not just individual queries but entire datasets released for open analysis.

The price is real. Strong privacy shrinks utility; the best synthetic generation algorithms are computationally expensive; and the hardness results from theoretical computer science say there is no general escape. The parameter ε\varepsilon is not a technical detail — it is the honest answer to the question "how much does this person's data actually matter to what you're publishing?"

As more data flows through the world, the ideas behind differential privacy and DP synthetic data are becoming the engineering bedrock of trustworthy AI and open science. Learning what ε\varepsilon costs is the first step toward spending it wisely.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/synthetic-data-dp/Content licensed under CC BY-NC 4.0.