API reference

The names exported at the top level (from synthpriv import ...) are re-exports of the objects documented below; the canonical documentation lives with the defining module.

Pipeline

Orchestrator pipeline: train, sample, evaluate and report.

class synthpriv.pipeline.EvaluationReport(data=<factory>)[source]

Bases: object

Container of the evaluate result.

Parameters:

data (dict[str, Any])

save(path)[source]

Generate the self-contained HTML report at path.

Parameters:

path (str | Path)

Return type:

Path

class synthpriv.pipeline.PrivacyPreservingSynthesizer(generator=None, generator_key=None, generator_kwargs=None, privacy_mechanism=None, privacy_metrics=None, utility_metrics=None, metric_options=None, random_state=0)[source]

Bases: object

Orchestrate generator + privacy mechanism + metrics.

Examples

>>> from synthpriv import PrivacyPreservingSynthesizer
>>> from synthpriv.privacy import NoPrivacy
>>> synth = PrivacyPreservingSynthesizer(
...     generator_key="ctgan",
...     generator_kwargs={"epochs": 100},
...     privacy_mechanism=NoPrivacy(),
...     utility_metrics=["ks_test", "correlation_mae", "ml_utility"],
...     privacy_metrics=["nndr", "mia_auc"],
... )
>>> synth.fit(df)
>>> synthetic = synth.sample(5000)
>>> report = synth.evaluate(df, synthetic)
>>> report.save("report.html")
Parameters:
generate(real_data, num_rows=1000, **kwargs)[source]

Shortcut: fit + sample.

Parameters:
  • real_data (DataFrame)

  • num_rows (int)

Return type:

DataFrame

assert_dp(declared_epsilon=None, *, tolerance=0.05)[source]

Validate the synthesizer’s DP guarantee against the accountant epsilon.

declared_epsilon defaults to the configured mechanism’s. The result reconciles the epsilon measured by the generator with the accountant’s (if they differ, the accountant’s after fit wins).

Parameters:
  • declared_epsilon (float | None)

  • tolerance (float)

Return type:

DpAssurance

evaluate(real_data, synthetic_data=None, num_rows=None, **sample_kwargs)[source]

Evaluate privacy and utility of the synthetic data vs the real one.

Parameters:
  • real_data (DataFrame)

  • synthetic_data (DataFrame | None)

  • num_rows (int | None)

Return type:

EvaluationReport

save_model(path)[source]

Persist the trained synthesizer (generator + privacy + metrics).

Generates path (the generator) and path.meta (privacy config, measured epsilon and metrics). load_model needs no retraining.

Parameters:

path (str | Path)

Return type:

Path

classmethod load_model(path)[source]

Rebuild a synthesizer persisted with save_model (no retraining).

Parameters:

path (str | Path)

Return type:

PrivacyPreservingSynthesizer

Sweep

Epsilon vs utility sweep to explore the privacy/utility trade-off.

Trains dp-gan with several privacy budgets, measures the real epsilon (RDP accountant) and evaluates the utility of each point. The result is a table ordered by measured epsilon and an HTML report with the curves.

class synthpriv.sweep.SweepResult(rows=<factory>, utility_metrics=<factory>, privacy_metrics=<factory>, generator='dp-gan')[source]

Bases: object

Result of an epsilon-utility sweep.

Each row is a dict: model, target_epsilon, measured_epsilon, util_<metric>, priv_<metric> and fit_seconds.

Parameters:
dataframe()[source]

Rows ordered by measured epsilon, ascending.

Return type:

DataFrame

save_report(path)[source]

HTML report with the measured-epsilon vs metric-value curve.

Parameters:

path (str | Path)

Return type:

Path

best_tradeoff(metric, threshold, lower_is_better=True)[source]

Best point: the one with the lowest measured epsilon meeting metric threshold.

For example best_tradeoff("util_correlation_mae", 0.05) returns the most private point whose utility (correlation MAE) is still within 0.05.

Parameters:
synthpriv.sweep.run_epsilon_sweep(real_data, epsilons=(0.1, 0.5, 1.0, 2.0, 5.0, 50.0), delta=1e-05, generator_key='dp-gan', generator_kwargs=None, utility_metrics=None, privacy_metrics=None, metric_options=None, num_rows=None, random_state=0)[source]

Train a DP generator for each of epsilons and record utility + real epsilon.

generator_key must be DP-capable (dp-gan or dp-copula). A very large epsilon (e.g. 50) is practically equivalent to “no DP”: it works as the architecture’s utility ceiling. The measured epsilon (RDP accountant for dp-gan, pure-DP composition for dp-copula) is the one plotted in the curve.

Parameters:
Return type:

SweepResult

Benchmark

Utility benchmark of DP generators vs non-DP SDV generators.

Trains a DP generator (dp-gan with DP-SGD or dp-copula with pure DP) with several budgets (measured epsilon) and also trains reference generators without privacy (ctgan, tvae, copula-gan, gaussian-copula) on the same dataset with the same evaluation. Result: a table with the utility of each point and an HTML report with the DP privacy/utility curve against the reference lines of each baseline.

Interpretation: if the DP generator at epsilon ~ no-DP (50) gets close to the best baseline, the architecture is the limitation; the distance at low epsilon is the cost of privacy.

class synthpriv.benchmark.BenchmarkResult(rows=<factory>, baselines=<factory>, utility_metrics=<factory>, privacy_metrics=<factory>, dp_generator='dp-gan')[source]

Bases: object

Result of run_benchmark.

Each row is a dict with: model (DP generator name or baseline name), kind (“dp” | “baseline”), target_epsilon and measured_epsilon (DP points only), util_<metric>, priv_<metric> and fit_seconds.

Parameters:
dataframe()[source]

Rows ordered: DP generator by measured epsilon, then the baselines.

Return type:

DataFrame

save_report(path)[source]

HTML report with the table and the DP vs baselines curves.

Parameters:

path (str | Path)

Return type:

Path

curve(generator, metric)[source]

(measured_epsilon, metric) series of the DP generator, ascending.

Parameters:
  • generator (str)

  • metric (str)

Return type:

list[dict[str, float]]

baseline_value(baseline, metric)[source]

Mean baseline value for a metric (single run or repeated).

Parameters:
Return type:

float | None

dp_value(metric, target_epsilon=None, generator=None)[source]

DP value at the most private point (or close to target_epsilon).

Parameters:
  • metric (str)

  • target_epsilon (float | None)

  • generator (str | None)

Return type:

float | None

utility_gap(metric, baseline, target_epsilon=None)[source]

Cost of privacy: how far the DP generator is from the baseline on a metric.

Positive = DP loses to the baseline; negative = DP wins.

Parameters:
  • metric (str)

  • baseline (str)

  • target_epsilon (float | None)

Return type:

float | None

best_dp_point(metric, threshold, lower_is_better=None)[source]

Lowest-epsilon DP point whose value meets the metric threshold.

Parameters:
  • metric (str)

  • threshold (float)

  • lower_is_better (bool | None)

Return type:

dict[str, float] | None

synthpriv.benchmark.run_benchmark(real_data, epsilons=(1.0, 2.0, 5.0, 10.0, 50.0), delta=1e-05, dp_generator='dp-gan', baselines=('gaussian-copula',), generator_kwargs=None, baseline_kwargs=None, utility_metrics=None, privacy_metrics=None, metric_options=None, num_rows=None, random_state=0)[source]

Benchmark a DP generator (at the given epsilons) against non-DP baselines.

dp_generator must be DP-capable (dp-gan or dp-copula). Baselines are trained once (they do not depend on epsilon) with their default configuration; for deep generators e.g. pass baseline_kwargs={"ctgan": {"epochs": 300}}. Uses the same metrics on all points.

Parameters:
Return type:

BenchmarkResult

Report

Self-contained HTML report generation.

synthpriv.report.details_cells(details)[source]

Serialize metric details into a readable line.

Parameters:

details (dict[str, Any])

Return type:

str

synthpriv.report.render_html(data, path)[source]

Render data (output of EvaluationReport.data) to HTML.

Parameters:
Return type:

Path

synthpriv.report.render_sweep_html(result, path)[source]

Render a SweepResult to a self-contained HTML with the curves.

Parameters:

path (str | Path)

Return type:

Path

synthpriv.report.render_benchmark_html(result, path)[source]

Render a BenchmarkResult to HTML with DP curves and baseline refs.

Parameters:

path (str | Path)

Return type:

Path

Core

Base contracts for synthetic data generators.

All synthpriv generators implement BaseSynthesizer. The minimal interface is fit + sample, so callers (pipeline, CLI, REST) are agnostic to the concrete algorithm.

class synthpriv.core.base.BaseSynthesizer(metadata=None, **kwargs)[source]

Bases: ABC

Common interface for any synthetic data generator.

Parameters:

metadata – Optional metadata (e.g. SDV SingleTableMetadata or a dict). If None, the generator infers it in fit.

property fitted: bool

True if fit completed successfully.

property model

Trained underlying model (implementation-specific).

abstractmethod fit(data)[source]

Train the generator on the real data.

Parameters:

data (DataFrame)

Return type:

BaseSynthesizer

abstractmethod sample(num_rows=1000, **kwargs)[source]

Generate num_rows synthetic records.

Parameters:

num_rows (int)

Return type:

DataFrame

fit_and_sample(data, num_rows=1000, **kwargs)[source]

Shortcut: fit + sample in a single step.

Parameters:
  • data (DataFrame)

  • num_rows (int)

Return type:

DataFrame

get_params()[source]

Reproducible configuration parameters (for save/resample).

Return type:

dict[str, Any]

save(path)[source]

Persist the trained generator to path (implementation-defined format).

Parameters:

path (str | Path)

Return type:

Path

classmethod load(path)[source]

Rebuild a trained generator from path.

Parameters:

path (str | Path)

Return type:

BaseSynthesizer

Generator registry.

Generators are registered with the @register_generator decorator and instantiated by name (key). This lets the pipeline, the CLI or a REST API resolve generators without importing each class directly.

exception synthpriv.core.registry.GeneratorNotFoundError[source]

Bases: KeyError

A generator that is not registered was requested.

class synthpriv.core.registry.GeneratorSpec(key, cls, description='', supports=('tabular',))[source]

Bases: object

Registry entry.

Parameters:
synthpriv.core.registry.register_generator(key, description='', supports=('tabular',))[source]

Decorator to register a class as a generator under key.

Parameters:
synthpriv.core.registry.list_generators()[source]

Keys of all registered generators.

Return type:

list[str]

synthpriv.core.registry.get_generator(key)[source]

Return the specification of a registered generator.

Parameters:

key (str)

Return type:

GeneratorSpec

synthpriv.core.registry.build_generator(key, *args, **kwargs)[source]

Instantiate a generator from its registered key.

Parameters:

key (str)

Return type:

BaseSynthesizer

Generators

Tabular generators.

Thin wrappers over SDV single-table synthesizers. They keep the BaseSynthesizer interface (fit/sample) and register in the registry.

class synthpriv.generators.tabular.CTGANGenerator(metadata=None, epochs=300, batch_size=500, embedding_dim=128, generator_dim=(256, 256), discriminator_dim=(256, 256), **kwargs)[source]

Bases: _SDVWrapper

CTGAN: conditional generative adversarial network for tabular data.

class synthpriv.generators.tabular.TVAEGenerator(metadata=None, epochs=300, batch_size=500, embedding_dim=128, **kwargs)[source]

Bases: _SDVWrapper

TVAE: variational autoencoder for mixed tabular data.

class synthpriv.generators.tabular.CopulaGANGenerator(metadata=None, epochs=300, batch_size=500, embedding_dim=128, generator_dim=(256, 256), discriminator_dim=(256, 256), **kwargs)[source]

Bases: _SDVWrapper

CopulaGAN: combines Gaussian copulas with the CTGAN architecture.

class synthpriv.generators.tabular.GaussianCopulaGenerator(metadata=None, default_distribution=None, **kwargs)[source]

Bases: _SDVWrapper

Classic Gaussian copula: fast, useful as baseline and for tests.

Differential privacy

Differential privacy (DP) mechanisms.

NoPrivacy gives no formal guarantee (empirical mitigation only). DPSGD configures the DP budget: DP-SGD with Opacus (RDP accountant) for the dp-gan generator, or the total pure-DP budget (delta 0, Laplace sub-mechanisms) for the dp-copula generator.

Golden rule: if is_dp is True but available is False, the pipeline refuses to proceed or warns clearly so that no guarantees are claimed that the code does not yet deliver.

class synthpriv.privacy.mechanisms.PrivacyMechanism(name='base', is_dp=False, available=True, notes='')[source]

Bases: object

Base for every privacy mechanism.

Parameters:
get_report()[source]

Descriptive dict to include in the evaluation report.

Return type:

dict[str, Any]

class synthpriv.privacy.mechanisms.NoPrivacy(name='no-privacy', is_dp=False, available=True, notes='No formal differential privacy guarantee. Re-identification risk must be assessed with the report metrics.')[source]

Bases: PrivacyMechanism

No formal DP mechanism.

Privacy is only mitigated empirically (generator quality + risk metrics). Permanent check warning: it gives no formal guarantee at all.

Parameters:
class synthpriv.privacy.mechanisms.DPSGD(name='dp-sgd', is_dp=True, available=True, notes="DP-SGD with Opacus's RDP accountant. The report epsilon is the real accumulated one after training, not the configured target. Requires the 'dp-gan' generator.", epsilon=1.0, delta=1e-05, noise_multiplier=None, max_grad_norm=1.0)[source]

Bases: PrivacyMechanism

Training with DP-SGD (Opacus).

After fit, used_noise_multiplier holds the applied noise and the RDP accountant returns the real accumulated epsilon (<= target when the sample size allows it). Uses the dp-gan generator.

Parameters:

Privacy accountant.

In phase 2 the real epsilon is computed by Opacus (RDP) during fit of the dp-gan generator and recorded here; the report shows that value, never a configured target.

class synthpriv.privacy.accountant.PrivacyAccountant(mechanism)[source]

Bases: object

Translates noise configuration into a real accumulated epsilon.

Parameters:

mechanism (PrivacyMechanism)

set_effective_epsilon(epsilon)[source]

Record the epsilon measured by the DP generator after fit.

Parameters:

epsilon (float)

Return type:

None

get_epsilon()[source]

Effective epsilon (None if there is no formal guarantee or it was not measured).

Return type:

float | None

report()[source]

Summary of the guarantee state for the report.

Return type:

dict[str, Any]

Formal assurance of the declared privacy.

assert_dp does not invent guarantees: it verifies the two operational facts that make Opacus’s RDP bound valid and checks that the privacy claim does not exceed what was accounted.

  1. Step integrity: each step() of the discriminator optimizer consumes DP budget. If more steps ran than the RDP accountant accounted, the guarantee is void and assert_dp fails. (Generator steps are only post-processing of the DP discriminator: they do not leak.)

  2. Budget not exceeded: the epsilon measured by the accountant must stay within declared_epsilon * (1 + tolerance). The operational window is [measured_epsilon, declared_budget]: any claim above what was measured would be technically defensible, equal or below would be inflated.

If the mechanism is NoPrivacy or the generator provides no measured epsilon, the result is fail with an explicit message: there is no formal guarantee to validate.

class synthpriv.privacy.assurance.DpAssurance(status='fail', declared_epsilon=None, measured_epsilon=None, delta=None, noise_multiplier=None, max_grad_norm=None, accounted_steps=None, actual_private_steps=None, steps_match=False, budget_respected=False, checks=<factory>, message='')[source]

Bases: object

Result of assert_dp: state of each check and the epsilon window.

Parameters:
  • status (str)

  • declared_epsilon (float | None)

  • measured_epsilon (float | None)

  • delta (float | None)

  • noise_multiplier (float | None)

  • max_grad_norm (float | None)

  • accounted_steps (int | None)

  • actual_private_steps (int | None)

  • steps_match (bool)

  • budget_respected (bool)

  • checks (list[dict[str, str]])

  • message (str)

property window: tuple[float | None, float | None]

Operational window (measured_epsilon, declared_budget).

raise_if_not_passed()[source]

Raise AssertionError if the guarantee is not validated (test/reporting).

Return type:

DpAssurance

synthpriv.privacy.assurance.assert_dp(generator, declared_epsilon=None, *, tolerance=0.05, delta=None)[source]

Validate the declared DP guarantee of a trained generator.

Parameters:
  • generator (BaseSynthesizer) – Trained DP-capable generator (dp-gan with DP-SGD step counters _disc_steps_accounted/_disc_steps_actual, or dp-copula and similar compositional pure-DP mechanisms without sequential steps — see components/accounted_epsilon).

  • declared_epsilon (float | None) – Claimed budget (defaults to the generator mechanism’s).

  • tolerance (float) – Relative margin allowed over the declared budget.

  • delta (float | None)

Return type:

DpAssurance

Split of the DP budget between training and marginals.

The dp-gan synthesizer consumes ecdf_epsilon on the marginals ECDFs and epsilon on DP-SGD training; the total guarantee is epsilon + ecdf_epsilon (additive and exact). This module helps split a total budget into those two items coherently.

class synthpriv.privacy.budget.BudgetSplit(total, margins_fraction, train, margins)[source]

Bases: object

Result of the split: train and margins items for dp-gan.

total (final composed guarantee) = train + margins. For dp-copula use DPCopulaGenerator directly with its internal fractions (margins_fraction/corr_fraction), which split the total automatically.

Parameters:
synthpriv.privacy.budget.split_budget(total_epsilon, margins_fraction=0.3)[source]

Divide total_epsilon into training and marginals for dp-gan.

A low margins_fraction (0.1-0.4) is usually enough for marginals with many rows; increase it on small datasets (Laplace histogram variance grows with less data) or when tails matter a lot and KS drops.

Parameters:
Return type:

BudgetSplit

ECDF with a formal differential privacy guarantee (Laplace / histogram).

Mechanism: histogram with Laplace-noisy counts for each numeric column.

  • Sensitivity per bin = 1 (adding/removing a row moves each count by at most 1) and the bins partition the data disjointly: by parallel composition, the whole column consumes a single epsilon (Laplace noise of scale 1/epsilon per bin).

  • Columns are mutually disjoint but not a partition of the same datum, so the total budget is split sequentially across columns: each column uses total_epsilon / n_columns.

  • The grid range is trimmed to the empirical 0.001/0.999 quantiles (with a margin), avoiding the publication of exact extremes; values outside that support are not emitted.

  • The quantile function (inverse of the noisy ECDF, smoothed monotonically and by linear interpolation) is post-processing of the DP output, so it consumes no extra budget. Emitting values from this inverse keeps the per-column DP guarantee; composition with the training DP-SGD gives the synthesizer’s total guarantee.

class synthpriv.privacy.dpecdf.DPEcdf(epsilon=1.0, bins=200, q_low=0.001, q_high=0.999, bounds=None)[source]

Bases: object

Private per-column ECDF based on a Laplace histogram.

Parameters:
  • epsilon (float) – DP budget of this column (0 < epsilon <= total marginals budget / n_columns). The rest of the synthesizer must compose it with the training epsilon (ecdf_epsilon + effective_epsilon).

  • bins (int) – Number of equal-width intervals over the trimmed support.

  • q_low/q_high – Quantiles (0..1) defining the grid support when bounds are not given; out-of-range values are aggregated at the edges via range clipping.

  • bounds (tuple[float, float] | None) – Public support (min, max) of the column. If provided, the grid is fixed and the mechanism is strictly pure DP (the guarantee does not depend on any prior data). If None, the support is derived from the empirical 0.001/0.999 quantiles of the data (with margin): practical, but the range itself reveals sample information — a warning is emitted and passing public bounds is recommended when available.

  • q_low (float)

  • q_high (float)

fit(values, rng=None)[source]

Build the private ECDF from values (one entry per row).

Parameters:
Return type:

DPEcdf

quantile(u)[source]

Inverse of the private ECDF over the uniform quantiles u (0..1).

Linearly interpolates between grid edges (DP post-processing).

Parameters:

u (ndarray)

Return type:

ndarray

report()[source]

Privacy state summary of the column (for the report).

Return type:

dict

DP generators

Tabular GAN trained with DP-SGD (Opacus), class-conditioned.

Classic DP-GAN construction: only the discriminator sees the data and trains with DP-SGD (gradient clipping + noise). The generator is post-processing of the discriminator, so the result is DP with the accounted epsilon.

For utility the AC-GAN scheme is used: the generator receives a condition vector (class of the most imbalanced column) and the discriminator has an auxiliary head that must predict it. The condition is sampled balanced in the generator step (CTGAN-style) so minority classes do not collapse to the majority one; when sampling, the empirical frequency is used to respect the marginal. Numerics use mode-specific normalization (ModeEncoder) to avoid flattening modes.

The RDP accountant translates noise, epochs and sample size into the real accumulated epsilon, exposed in accounted_epsilon after fit.

class synthpriv.dp.gan.DPSGDGenerator(privacy=None, epochs=100, latent_dim=64, hidden_dim=256, layers=2, learning_rate=0.0002, batch_size=128, dropout=0.0, num_modes=3, clip_value=3.0, condition_column=None, aux_lambda=1.0, generator_steps=2, numeric='mode', rectify_marginals=False, ecdf_epsilon=None, ecdf_bins=200, ecdf_bounds=None, label_smoothing=0.0, random_state=0, **kwargs)[source]

Bases: BaseSynthesizer

Tabular generator with a formal differential privacy guarantee.

Parameters:
  • privacy (DPSGD | None) – DPSGD mechanism with target epsilon/delta. If noise_multiplier is set, that noise is used; otherwise Opacus computes it to reach the budget from epochs/batch_size/n_samples.

  • num_modes (int) – Gaussian Mixture modes per numeric column. 1 = plain z-score. A low value (3-5) captures multimodality; raising it too much burdens the generator with extra dimensions.

  • condition_column (str | None) – Categorical column conditioning generation. None picks the most imbalanced one (lowest entropy). The generator step samples the condition balanced (CTGAN-style) so minority classes are learned, and generation samples with the real empirical frequency.

  • aux_lambda (float) – Weight of the auxiliary losses (discriminator classifier and direct consistency between the generated class and its condition).

  • generator_steps (int) – Generator steps per discriminator step (the DP budget only counts the discriminator).

  • ecdf_epsilon (float | None) – DP budget for the marginals ECDFs (only with numeric="uniform"). Distributed equally across numeric columns (Laplace histogram, parallel composition by bins and sequential across columns). The synthesizer’s total guarantee is the sequential composition of this budget with the training one: total_epsilon = epsilon(accumulated) + ecdf_epsilon (the report exposes it in accountant.ecdf_epsilon/ total_epsilon). None uses the raw empirical ECDF (no formal guarantee on the marginal).

  • epochs (int)

  • latent_dim (int)

  • hidden_dim (int)

  • layers (int)

  • learning_rate (float)

  • batch_size (int)

  • dropout (float)

  • clip_value (float)

  • numeric (str)

  • rectify_marginals (bool)

  • ecdf_bins (int)

  • ecdf_bounds (tuple[float, float] | None)

  • label_smoothing (float)

  • random_state (int)

fit(data, **kwargs)[source]

Train the generator on the real data.

Parameters:

data (DataFrame)

Return type:

DPSGDGenerator

sample(num_rows=1000, **kwargs)[source]

Generate num_rows synthetic records.

Parameters:

num_rows (int)

Return type:

DataFrame

assert_dp(declared_epsilon=None, *, tolerance=0.05, delta=None)[source]

Validate that the declared DP guarantee is not exceeded (steps + budget).

Parameters:
Return type:

DpAssurance

get_params()[source]

Reproducible configuration parameters (for save/resample).

Return type:

dict[str, Any]

save(path)[source]

Persist config + encoder + weights + DP accounting in one file.

The real accumulated epsilon and the applied noise are kept: when reloading, the declared DP guarantee is the same as when trained.

Parameters:

path (str | Path)

Return type:

Path

classmethod load(path, **overrides)[source]

Rebuild a trained generator and its DP accounting from path.

Parameters:

path (str | Path)

Return type:

DPSGDGenerator

Gaussian copula with differential privacy (dp-copula).

Fully parametric, fast model that attacks the dp-gan weak spot: the dependence structure. The guarantee is pure DP (delta 0) because all sub-mechanisms are Laplace (no RDP Gaussian noise):

  1. Marginals: DPEcdf per numeric column (Laplace histogram, parallel composition by bins; budget margins_fraction * epsilon split sequentially across columns).

  2. Dependence (copula): over the real Gaussians z = Phi^-1(rank) (deterministic transformation from n, with values bounded by B = Phi^-1((n-0.5)/n), a public quantity), the sample covariance is perturbed entry-wise with Laplace noise of scale (B^2 / n) / epsilon_entry. Sensitivity per entry <= B^2/n (removing a row changes one per-row term of the sum). As entries share the data, the composition is sequential across the d(d-1)/2 correlations: epsilon_entry = corr_fraction * epsilon / n_entries. It is then projected to the correlation sphere (PSD + diagonal 1).

  3. Categoricals: Laplace frequencies (parallel composition across categories, sequential across columns; budget the rest of epsilon).

Sampling is post-processing of these DP outputs: private Gaussian copula N(0, R) -> u = Phi(z) -> DPEcdf.quantile(u) and private multinomials. Dependence is the main gain over independent marginals, and its cost is total: the budget is split between marginals, copula and categoricals according to the configurable fractions.

class synthpriv.dp.copula.DPCopulaGenerator(privacy=None, margins_fraction=0.4, corr_fraction=0.4, bins=200, bounds=None, random_state=0, **kwargs)[source]

Bases: BaseSynthesizer

Differentially private Gaussian copula (parametric and fast).

Parameters:
  • privacy (DPSGD | None) – DPSGD mechanism with the total epsilon budget. For this generator DP is pure (delta 0): the mechanism’s delta only applies to the dp-gan’s DP-SGD.

  • margins_fraction (float) – Fraction of epsilon dedicated to the numeric marginals (split equally across columns).

  • corr_fraction (float) – Fraction dedicated to the copula’s correlation matrix. With categorical columns, the rest goes to their frequencies.

  • bins/bounds – Grid and public support of the DPEcdf``s (see ``DPEcdf).

  • bins (int)

  • bounds (dict[str, tuple[float, float]] | None)

  • random_state (int)

fit(real_data)[source]

Train the generator on the real data.

Parameters:

real_data (DataFrame)

Return type:

DPCopulaGenerator

sample(num_rows=1000, **kwargs)[source]

Generate num_rows synthetic records.

Parameters:

num_rows (int)

Return type:

DataFrame

get_params()[source]

Reproducible configuration parameters (for save/resample).

Return type:

dict[str, Any]

save(path)[source]

Persist the trained generator to path (implementation-defined format).

Parameters:

path (str | Path)

Return type:

Path

classmethod load(path, **overrides)[source]

Rebuild a trained generator from path.

Parameters:

path (str | Path)

Return type:

DPCopulaGenerator

Tabular encoders toward a uniform numeric space (and their inverse).

  • TabularEncoder: z-score + one-hot (basic, stable).

  • ModeEncoder: mode-specific normalization with Gaussian Mixture + imbalanced-class conditioning (CTGAN-style) for better utility.

class synthpriv.dp.encoder.TabularEncoder[source]

Bases: object

Transform a DataFrame to a continuous array and rebuild it.

The numeric block uses the first num_dims components; categorical columns are one-hot-encoded afterwards, their intervals exposed in categorical_spans as (start, end) lists.

class synthpriv.dp.encoder.ModeEncoder(num_modes=5, clip_value=3.0, condition_column=None, numeric='mode', dp_ecdf_epsilon=None, ecdf_bins=200, ecdf_bounds=None)[source]

Bases: object

Higher-utility tabular encoder, inspired by CTGAN.

Numerics: two normalization modes.

  • mode (mode-specific): each value is assigned to the most probable mode of a Gaussian Mixture (per column), normalized within the mode and encoded together with a mode one-hot. Captures multimodal distributions that the z-score flattens. num_modes=1 degenerates to z-score.

  • uniform (empirical-CDF/rank gaussianization): maps each value to its percentile rank and then to a standard normal value via Phi^-1 (per-column gaussianization). The inverse applies Phi and the empirical quantile, so the marginal is reconstructed by construction if the generator emits standard normal values (easier marginal to learn, no tanh saturation).

Categoricals: one-hot. Additionally, the most imbalanced categorical column is chosen as condition_column_ (lowest entropy) for the generator’s AC-GAN conditioning; its one-hots are exposed in condition_vectors/sample_conditions.

The output uses blocks: each numeric block is [normalized value (+ mode one-hot in ``mode mode)]`` and each categorical is its one-hot.

Parameters:
rectify(X)[source]

Rectify continuous numeric vectors to uniform marginals.

For each uniform block it replaces the value with its percentile rank within the sample (rank-0.5)/n passed through Phi^-1. As it is a monotone per-column transformation, the sample copula (rank correlations / dependence structure) is preserved untouched while each column’s marginal becomes exactly uniform: the inverse then returns the real empirical quantiles. Useful to correct the generator’s marginal bias without touching the learned joint structure. Applies to uniform numerics; the rest of the vector is not modified.

Parameters:

X (ndarray)

Return type:

ndarray

condition_vectors(data)[source]

One-hot of each real row’s class (for the discriminator).

Parameters:

data (DataFrame)

Return type:

ndarray | None

sample_conditions(n, rng)[source]

One-hot classes to generate n rows, with the empirical frequency.

Parameters:
Return type:

ndarray | None

Metrics

Metric result model and threshold evaluation.

class synthpriv.metrics.base.MetricResult(name, description='', value=None, threshold=None, direction='lower_is_better', status='reported', message='', details=<factory>)[source]

Bases: object

Result of a single metric.

Parameters:
synthpriv.metrics.base.evaluate_status(value, threshold, direction)[source]

Decide PASSED/FAILED/REPORTED status by comparing against a threshold.

Parameters:
Return type:

tuple[str, str]

synthpriv.metrics.base.summarize(results)[source]

Count statuses for the report summary.

Parameters:

results (dict[str, MetricResult])

Return type:

dict[str, int]

Metric registry and orchestration functions.

synthpriv.metrics.core.register_metric(fn, name=None)[source]

Register a metric function under name (sys.modules[fn.__module__].__name__).

Parameters:
synthpriv.metrics.core.evaluate_metrics(real, synth, metric_names, metric_options=None)[source]

Run a list of metrics over (real, synth).

Return type:

dict[str, MetricResult]

synthpriv.metrics.core.metrics_summary(*groups)[source]

Combined summary of several metric groups.

Parameters:

groups (dict[str, MetricResult])

Return type:

dict[str, int]

Utility metrics: how close the synthetic data is to the real one.

synthpriv.metrics.utility.ks_test(real, synth, significance=0.05)[source]

Kolmogorov-Smirnov test per numeric column.

Null hypothesis: the marginal distributions match. p >= significance implies equality cannot be rejected -> the column is useful.

Parameters:
  • real (DataFrame)

  • synth (DataFrame)

  • significance (float)

Return type:

MetricResult

synthpriv.metrics.utility.correlation_mae(real, synth, max_mae=0.05)[source]

Mean absolute error between Pearson correlation matrices.

Parameters:
  • real (DataFrame)

  • synth (DataFrame)

  • max_mae (float)

Return type:

MetricResult

synthpriv.metrics.utility.ml_utility(real, synth, target=None, model=None, test_size=0.3, min_score=0.6)[source]

Train-on-Synthetic-Test-on-Real (TSTR) performance.

Trains a model on the synthetic data and evaluates it on the real one (TSTR); also trains and evaluates on real data (TRTS) as the maximum reachable reference. value is the TSTR metric.

Parameters:
  • real (DataFrame)

  • synth (DataFrame)

  • target (str | None)

  • test_size (float)

  • min_score (float)

Return type:

MetricResult

Privacy metrics: re-identification and inference risk.

synthpriv.metrics.privacy.nndr(real, synth, threshold=0.8, sample=2000)[source]

Nearest Neighbor Distance Ratio.

For each synthetic row i: ratio = d(nearest real) / d(nearest synthetic excluding itself). Ratios << 1 imply near-duplicates of real records among the synthetic ones (re-identification risk). Values >= 1 indicate the synthetic points are not stuck to the real ones.

Parameters:
  • real (DataFrame)

  • synth (DataFrame)

  • threshold (float)

  • sample (int)

Return type:

MetricResult

synthpriv.metrics.privacy.mia_auc(real, synth, threshold=0.7, folds=5)[source]

Membership inference attack.

Trains a classifier to distinguish real from synthetic rows (cross-validation). AUC ~0.5 = indistinguishable (good); high AUC = the synthetic rows are distinguishable and an attacker could infer membership.

Parameters:
  • real (DataFrame)

  • synth (DataFrame)

  • threshold (float)

  • folds (int)

Return type:

MetricResult

synthpriv.metrics.privacy.anonymeter_discovery(real, synth, n_attacks=5, threshold=0.1)[source]

Univariate discovery ratio: % of real rows “recovered” in the synthetic ones.

Parameters:
  • real (DataFrame)

  • synth (DataFrame)

  • n_attacks (int)

  • threshold (float)

Return type:

MetricResult

synthpriv.metrics.privacy.anonymeter_inference(real, synth, n_attacks=3, threshold=0.1)[source]

Sensitive-attribute inference attack from auxiliary attributes.

Parameters:
  • real (DataFrame)

  • synth (DataFrame)

  • n_attacks (int)

  • threshold (float)

Return type:

MetricResult

synthpriv.metrics.privacy.anonymeter_linkability(real, synth, n_attacks=3, threshold=0.1)[source]

Linkability attack: join two attribute halves to re-identify.

Parameters:
  • real (DataFrame)

  • synth (DataFrame)

  • n_attacks (int)

  • threshold (float)

Return type:

MetricResult

Utilities

Shared utilities (logging, validation).

synthpriv.utils.get_logger(name='synthpriv')[source]

synthpriv logger (child of the synthpriv root).

Parameters:

name (str)

Return type:

Logger

synthpriv.utils.check_fitted(instance)[source]

Raise if the generator/method has not been trained yet.

Return type:

None

synthpriv.utils.timed_block(label)[source]

Record how long a block takes; the returned value reports elapsed seconds.

Parameters:

label (str)

Command-line interface

synthpriv command-line interface.

Usage:

synthpriv generate –data real.csv -m ctgan –rows 5000 -o synthetic.csv synthpriv evaluate –real real.csv –synthetic synthetic.csv -o report.html