Skip to main content

synthweave

Synthetic tabular data that does not require real microdata.

PyPI CI Python 3.10+ License: MIT

Quickstart · Why · How it works · Three ways to use it · FAQ · Full guide


Most synthetic data tools fit a model to data you already have. That is no help when you have none, cannot get access, or are not allowed to put what you have into a model.

synthweave generates from a schema you declare by hand, then optionally layers in statistical structure, realistic messiness, and identifiers that link correctly across tables. It works the other way round too:

What you have What synthweave does
Nothing but domain knowledge Generates from rules you declare
Real microdata Fits on it, like any other synthesis tool
Only published statistics Expands those into a training frame
None of the above Pulls public US Census data as a stand-in

New here? Every term below is defined in the full guide: glossary, complete syntax reference, and an 8-step tutorial. This README is the fast tour.

Install

pip install synthweave
Other install options

Latest commit, ahead of the last PyPI release:

pip install "git+https://github.com/emmanuelwunjc/synthweave.git"

Local development:

git clone https://github.com/emmanuelwunjc/synthweave.git
cd synthweave
pip install -e ".[dev]"

Optional extras: [parquet] for Parquet output, [pii] for Faker-backed names and SSNs.

Quickstart

import synthweave as sw

people = sw.Entity("person", count=10_000, attributes={"education": ["HS", "College"]})
table  = sw.Table("roster", grain="person", carry="*")
result = sw.Pipeline(sw.Schema([people], [table])).run()

result["roster"].head()

Ten thousand rows. No data file, no API key, no setup beyond pip install.

That is the whole shape of synthweave: describe a population once (Entity), describe a table that records it (Table), run it (Pipeline). Everything below is a variation on those three lines.

Why synthweave

synthweave GAN/VAE synthesizer Fake-data generator (Faker)
Needs real data to start No Always No
Preserves column relationships Yes, declared or learned Yes, with training data No
Cross-table IDs that link Yes, derived, no lookup table Not built in Not built in
Deterministic output Yes, per seed, any chunk size Usually not Usually yes
Config provenance tracking Yes Not attempted Not attempted

The gap it targets: you need data that looks and behaves like a real linked administrative dataset (a school roster, a wage file, a benefits record, all describing the same people), and you either cannot access real microdata or are only prototyping and do not want to touch it yet.

How it works

A pipeline is up to four stages, each optional past the first:

Stage Does what Needs real data
Generate Draws every attribute and column from its rule No
Synthesize Refits columns with a statistical model against a chosen structure source No
Link Attaches identifiers that match the same person across tables No
Noise Corrupts values deliberately (typos, blanks, scan errors) No

Three properties hold it together.

Nothing is random. Every value is a pure function of (seed, stable key, salt). Same seed, same output, forever, regardless of chunk size. That is what lets two tables generated years apart still link correctly with no lookup table involved.

One caveat for the statistically minded

The model-fitting step uses scikit-learn's decision trees, which do not take an explicit seed from synthweave. This has never produced observed non-determinism, but unlike everything else here it is not derived from the seed by construction. Worth knowing if you rely on bit-for-bit reproducibility as a hard guarantee rather than an extremely well-tested one.

Structure has to come from somewhere. A model fit on independently generated columns learns nothing, so the synthesizer always names a structure source:

  • Declared learns from relationships your own Conditional rules already put in the data. Use when you have domain knowledge but no data.
  • Empirical learns from a real sample you supply.
  • Prior learns from published statistics. No rows needed.

Every config number says where it came from. Wrap a guess in sw.modeled(0.08, "not yet sourced") and a real figure in sw.cited(0.82, "NCES Table 219.10"). Then result.unjustified() lists every number nobody has defended yet, so a config is auditable instead of a wall of magic numbers.

There is also a reduced-typing shorthand for common cases (a bare list instead of sw.Choice([...]), grain="person" instead of sw.PerEntity("person")). See the fast path.

Three ways to use it

Pick the one matching what you actually have. All three share this setup:

people = sw.Entity("person", count=10_000, attributes={"education": ["HS", "College"]})
wages  = sw.Table("wages", grain="person", carry=["education"], columns={"wage": 0.0})
schema = sw.Schema([people], [wages])

1. Domain knowledge only

Declare the relationship yourself. Conditional puts real structure in before anything is fitted, so wage depends on education because you said so, not because a model guessed. No synthesizer= needed.

wages = sw.Table("wages", grain="person", carry=["education"], columns={
    "wage": sw.Conditional("education", {
        "HS":      sw.Normal(38_000, 9_000, low=0),
        "College": sw.Normal(64_000, 18_000, low=0),
    }),
})
result = sw.Pipeline(sw.Schema([people], [wages])).run()

2. A real sample you want more of

Keep the 0.0 placeholder (the synthesizer overwrites it) and hand over your real data. It learns the actual relationships and generates rows that carry them.

real_sample = pd.read_csv("my_real_wages.csv")   # columns: education, wage

result = sw.Pipeline(
    schema,
    synthesizer=sw.CARTSynthesizer(["wage"], predictors=["education"], structure=real_sample),
).run()

3. Published statistics, or nothing at all

A report says "60% HS, 40% College"? Use it directly, no rows needed:

result = sw.Pipeline(
    schema,
    synthesizer=sw.CARTSynthesizer(["education"], structure={"education": {"HS": 0.6, "College": 0.4}}),
).run()

If you do not even have that, borrow real government survey data as a stand-in:

from synthweave.connectors.acs_pums import fetch_pums

acs = fetch_pums(["AGEP", "PINCP"], state="NY").rename(columns={"AGEP": "age", "PINCP": "income"})
synthesizer = sw.CARTSynthesizer(["age", "income"], structure=acs)

Worth knowing: this is real data, but not your population. It is a Census survey sample for a given state and year, so treat it as a plausible reference distribution, not a guarantee it matches your target demographics. Needs a free Census API key as CENSUS_API_KEY.

All three run end to end in examples/three_layers_data_availability.py.

FAQ

Is the output actually private, or safe to share?

synthweave does not yet include formal disclosure-risk metrics or differential privacy. Treat output as "structurally realistic, not formally privacy-audited."

One mechanism is worth naming explicitly. When a synthesized column's structure source is Empirical, each output value is sampled from the actual real values that landed in the same decision-tree leaf as the row being synthesized, not a perturbed or resampled one. With few real rows behind a narrow leaf, that is a genuine attribute-disclosure vector if the columns you condition on are also identifying. Control leaf size with min_samples_leaf (see the guide).

Does my real data ever leave my machine?

No. Empirical(df) and fetch_pums(...) run entirely locally. The Census connector calls the public Census API to fetch government-published data. It never sends anything.

How do I check synthesis preserved my real data's structure?

sw.fidelity_report(synth_df, real_df, columns=[...]).

Declared and Prior have no original to check against, since the schema declares whatever structure it wants. Empirical is different: it fits on real data, and the point is that output carries the same marginal distributions, category shares, and pairwise associations the input had. The report compares both, and with synthesizer= also reports how many rows kept a pre-synthesis placeholder because their leaf had no donors.

Is this like Faker?

No. Faker generates realistic individual values with no relationship between columns or rows. synthweave generates whole linked datasets: multiple tables, the same person's ID matching across all of them, and real statistical relationships between columns.

Can I trust the numbers in someone else's config?

Run result.unjustified(). Anything not tagged cited with a real source is either a deliberate choice (user-provided) or an unverified guess (modeled), and you can see which at a glance.

What if none of the three structure sources fit my case?

Every stage is a Protocol with a registry. Implement the shape, register it, no library edits or inheritance required. See "Extending it" in the guide.

Scale

Stages pass chunks, not whole tables, so memory stays flat as row counts grow. Measured at roughly 200,000 rows/s with peak RSS around 0.44 GB, streamed straight to disk, on a 3.2 million row run.

The memory figure is the load-bearing one: it does not grow with row count, so tens of millions of rows is a matter of time, not RAM. Throughput varies with machine load, so treat it as an order of magnitude, not a benchmark.

sw.Pipeline(schema, chunk_size=200_000).run_to("out/", format="parquet")

Status

v0.2. Pre-1.0: the public API may still change between minor versions.

Every capability described above is present and tested. One implementation ships per stage, and the interfaces are the real commitment, so adding implementations should not require restructuring.

The API freezes at v1.0, which also requires zero known bugs in the "produces plausible-looking wrong data without raising" class. Those are tracked openly in the issue tracker along with the design reasoning behind them.

Not yet included: disclosure-risk metrics, differential privacy, R interop, deep generative synthesizers.

Development

pip install -e ".[dev]"
pre-commit install
PYTHONPATH=src python -m pytest tests/ -q

Tests run offline and go through the public API only, so internal stage code can be rewritten without touching them. Every bug fix is checked by reverting it and confirming a test goes red: a fix nothing catches is not a fix. See CONTRIBUTING.md.

Where this came from

The design generalizes two patterns proven in a real engagement building linked synthetic administrative data: deterministic hash-derived identifiers, and provenance-tagged config. It draws on pseudopeople for realistic noise models, and synthpop / tidysynthesis for sequential CART synthesis. What is new is combining them on a schema you define yourself, with no real data required to start.

Also runs in a browser. synthweave-frontend runs this pipeline locally, checks the linking guarantee against the produced data, and re-runs your config at three chunk sizes to prove the output is identical.

License

MIT

Metadata

Release files for synthweave 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for synthweave 0.2.0
File Size Uploaded Provenance Actions
synthweave-0.2.0.tar.gz 116.1 kB With provenance Details for synthweave-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for synthweave 0.2.0
File Interpreter ABI Platform Actions
synthweave-0.2.0-py3-none-any.whl Python 3 none any Details for synthweave-0.2.0-py3-none-any.whl

Total release size: 182.7 kB

Release files / synthweave-0.2.0.tar.gz

Download URL synthweave-0.2.0.tar.gz
Size 116.1 kB
Tags Source
SHA-256 checksum
How to use checksums
897bc7b5777a7f638151432404484092fae56c68b1e2ebc08a4dd7ce77f9e739
BLAKE2b-256 checksum
How to use checksums
7bebf0ab04b53efd043c40ce021e2e22137e20edb5e05f0caf418c554d7af17f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

Release files / synthweave-0.2.0-py3-none-any.whl

Download URL synthweave-0.2.0-py3-none-any.whl
Size 66.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b600d3fc56d03f51c1cc5ca36706556405580d5949c08e57f16a12de29c8a72e
BLAKE2b-256 checksum
How to use checksums
11d0d8267963e0c3a4f4574f5ace71d0794d7509e8cb84d70cf93ef102ec85f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

Security for synthweave 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for synthweave-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page