synthcity: A Plugin Registry for Tabular Synthetic Data, With Evaluation Built In
A library for generating and evaluating synthetic tabular data for privacy, fairness and data augmentation.
At a glance
- What is it?
- synthcity collects GAN, VAE, normalizing flow, Bayesian network and LLM-based tabular generators behind one Plugins registry, then scores them with the same benchmark harness. It is aimed at researchers and data scientists who need to compare generators rather than commit to one, and it assumes your missing values are already imputed.
- Who is it for?
- Adopt synthcity if you need to compare several tabular generators on one dataset through a single API, or if you need survival analysis or time series generation with an evaluation harness attached. Do not adopt it as a drop-in replacement for a single production generator you have already tuned, and do not expect it to handle missing data, since the README states values must be imputed first (HyperImpute is the lab's own suggestion).
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 147 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Problem synthcity Solves Is Comparison, Not Generation
Generating synthetic tabular data is not the hard part any more. The hard part is deciding which of a dozen published methods to use on a specific table with mixed categorical and continuous columns, and then defending that choice with numbers. synthcity's answer is a plugin registry. Every generator, whether it is a GAN variant such as AdsGAN, CTGAN, PATEGAN or DP-GAN, a VAE such as TVAE or RTVAE, a Bayesian network such as PrivBayes, a random forest wrapper (arfpy), or an LLM-based method (GReaT), is registered under a name and reached through Plugins().get("name"). The same registry also carries time series generators (TimeGAN, FourierFlows, TimeVAE), static survival analysis generators (SurvivalGAN, SurVAE), domain adaptation (RadialGAN) and image generators.
The audience follows from that design. This is a library for people running experiments: comparing methods, reproducing a paper baseline, or checking whether a privacy-focused generator such as DECAF or DP-GAN costs more utility than a general-purpose one. It is less obviously a library for someone who has already chosen CTGAN and wants a thinner dependency. The registry, the data loaders and the benchmark module all exist to make swapping methods cheap, and if you never swap methods you are paying for machinery you do not use.
One Registry, Many Data Loaders, and a Benchmark Module on Top
The architecture visible in the README is three layers. At the bottom sit the generator implementations, grouped by category. In the middle sit data loaders that normalise the shape of the input: GenericDataLoader for ordinary tables, SurvivalAnalysisDataLoader for data with a target column and a time-to-event column, TimeSeriesDataLoader for temporal data with observation times and static covariates, and ImageDataLoader for image datasets. At the top sit two entry points: the Plugins registry and the Benchmarks module.
The data flow is consistent across categories. You construct a loader, call Plugins().get("name") to obtain a model, call fit on the loader, then call generate(count=N). The survival example builds a SurvivalAnalysisDataLoader from the lifelines rossi dataset with target_column="arrest" and time_to_event_column="week", fits survival_gan, and generates ten rows. The time series example loads Google stocks data through GoogleStocksDataloader, wraps it in TimeSeriesDataLoader with temporal_data, observation_times, static_data and outcome, fits timegan, and generates. The image example wraps an MNIST dataset in an ImageDataLoader, samples 100, fits image_cgan, and unpacks the result with .unpack().numpy().
GenericDataLoader is where the evaluation hooks live. The benchmark example constructs it with target_column="target" and sensitive_columns=["sex"], which tells the benchmark which column to score predictive performance against and which column to treat as sensitive for fairness metrics. That single constructor call is the junction between generation and evaluation.
Installation and the Plugin Categories You Can List
Installation is a single PyPI command, with extras for optional surfaces:
pip install synthcity
There are three documented extras. pip install synthcity[testing] adds unit-testing support, pip install synthcity[goggle] adds GOGGLE support, and pip install synthcity[all] pulls in every extension. Installing from a checkout is pip install . from the repository root. The package metadata states Python 3.8 or later.
The first thing to run after install is the category listing, because it tells you which plugins your installation actually exposes:
from synthcity.plugins import Plugins Plugins(categories=["generic", "privacy"]).list()
The README shows the same call with different category lists for survival analysis (["generic", "privacy", "survival_analysis"]) and time series (["generic", "privacy", "time_series"]). Note the pattern: the category lists in the examples are not exclusive, they are additive. Generic and privacy plugins appear in all three. If you want the full inventory, the README does not print one call that returns everything, so you either list categories individually or read the plugin source tree.
Evaluation Is the Actual Differentiator, and It Runs Through Benchmarks.evaluate
The README spends as much space on scoring as on generation, and that is the right emphasis. Benchmarks.evaluate takes a list of tuples in the form (testname, plugin_name, plugin_args), a data loader, a synthetic_size, a metrics dictionary and a repeats count. The documented example scores adsgan, ctgan and tvae against the diabetes dataset with synthetic_size=1000, metrics={"performance": ["linear_model"]} and repeats=3, then prints the result with Benchmarks.print(score).
Two details in that signature matter more than they look. First, repeats=3 means each configuration is trained and scored three times, so the comparison is against variance rather than a single run. That is the correct way to compare GAN-based generators, which are sensitive to initialisation, but it also means your wall-clock cost is three times the single-run cost before you have added any plugins. Second, the metrics argument is a dictionary keyed by metric family, and the README only demonstrates the "performance" family with "linear_model". The feature list claims several evaluation metrics for correctness and privacy, but the README does not enumerate the privacy metric names. If privacy is why you are here, plan to read the benchmark module source or the Read the Docs site before you can write that dictionary.
The loader's sensitive_columns argument is the other half of this. Passing sensitive_columns=["sex"] is what makes fairness-oriented scoring possible at all; omit it and the benchmark has no column to condition on.
Missing Data, Image Architectures and the Limits of the Registry
The README carries two explicit warnings, and both are load-bearing.
The first: synthcity does not handle missing data, and values must be imputed first. HyperImpute, from the same lab, is named as the tool for that. This is not a minor caveat. Real tabular datasets are usually incomplete, and the correct imputation strategy interacts with the generator: a GAN trained on mean-imputed columns learns the imputation artefact as if it were signal. The library's position is that imputation is a separate concern, which is defensible as a design boundary but means synthcity is never a one-command pipeline.
The second warning concerns images. The README states plainly that the architectures used for image generators are not state-of-the-art, and points readers to extending suggest_image_generator_discriminator_arch in the convnet.py module if they want different ones. That is an honest disclosure, and it also tells you the image category is a demonstration surface rather than a production one. If your problem is images, the tabular machinery is not what you want anyway.
A third limitation is structural rather than stated. The registry is as broad as its plugin list, and breadth has a cost: each generator carries its own dependency footprint, and the [all] extra exists precisely because not every plugin is cheap to install. Choosing a narrow category list at install time is the practical mitigation.
How synthcity Differs From the Single-Method Libraries It Wraps
The obvious alternative is to use a single generator's own library directly. CTGAN, for instance, is distributed by the SDV project, which also provides its own metadata model for describing column types and constraints, and its own evaluation module. The difference in approach is real and worth stating precisely.
SDV's design centres on a metadata object that you declare once and that every model respects: it is a modelling framework with a data-description layer at its core. synthcity's design centres on a registry and a loader: it is a comparison harness that happens to include models. In synthcity you express column roles through the loader (target_column, sensitive_columns) and through plugin arguments; in SDV you express them through a metadata schema that the whole library reads.
That distinction changes what each is good at. If you have one dataset, one chosen model, and a need to encode column semantics carefully, the metadata-first approach gives you more control over types and constraints. If you have one dataset and an open question about which method to use, synthcity's registry plus Benchmarks.evaluate with repeats gives you an answer with variance attached. Note also that synthcity's domain coverage is wider than tabular generation alone: survival analysis and time series generators sit in the same registry, and no single tabular generator library covers those.
Maintenance, Release Cadence and the Apache-2.0 Boundary
The release history shows an uneven cadence. v0.2.10 landed in March 2024, v0.2.11 in September 2024, and v0.2.12 in May 2025. The gap between the last two releases is roughly eight months, and the repository's most recent push is later than the last tagged release. That pattern is consistent with a research lab's library: active, but not on a fixed schedule, and not promising a deprecation policy.
Practically, that means you should pin a version. The plugin registry is a public API surface, and a registry that gains plugins over time can also change plugin names or default arguments. Pinning synthcity in your requirements file and upgrading deliberately, with a benchmark run before and after, is the only way to know whether a version bump changed your numbers.
The licence is Apache-2.0, which is permissive and includes an explicit patent grant. The README also links an arXiv paper (2301.07573), which is the citation route if you use this in published work. Nothing in the supplied material describes commercial support, a governance model or a contributor agreement, so treat the licence as the whole of the legal story and check the plugin dependencies separately: Apache-2.0 on synthcity does not relicense the third-party packages its plugins pull in.
Serialization and the Shape of a Real Workflow
The README's serialization section shows save and load helpers from synthcity.utils.serialization, used alongside Plugins. The intent is that a fitted generator is an artefact you persist rather than retrain. That matters given the cost profile: Benchmarks.evaluate with repeats=3 trains each configuration three times, so a benchmark sweep on a large table is the expensive step, and the fitted model you keep is the one you actually deploy.
Putting the pieces together, a defensible workflow looks like this. Impute first, outside synthcity, because the library will not do it. Build a GenericDataLoader with target_column and sensitive_columns set, since those two arguments determine what the benchmark can score. Run Plugins(categories=[...]).list() to see what is installed rather than assuming a plugin name exists. Run Benchmarks.evaluate across a shortlist with repeats greater than one, because single-run comparisons between GAN-based generators are not comparisons. Then fit the winner once more on the full data and persist it with save.
The step most teams will skip is the sensitive_columns argument, and it is the one that decides whether the fairness half of the library does anything for you.
Editorial conclusion
Adopt synthcity if you need to compare several tabular generators on one dataset through a single API, or if you need survival analysis or time series generation with an evaluation harness attached. Do not adopt it as a drop-in replacement for a single production generator you have already tuned, and do not expect it to handle missing data, since the README states values must be imputed first (HyperImpute is the lab's own suggestion). Before committing, verify three things on your own data: that your chosen plugin trains within your memory budget, that the metric set you pass to Benchmarks.evaluate covers the privacy question you actually care about, and that the plugin you pick is not one of the image generators, whose architectures the README explicitly describes as not state-of-the-art.
Community notes