On this page
- Quick comparison
- Faker: row-level fake data, no relationships
- SDV (Synthetic Data Vault): statistical models from real data
- Misata: story-driven relational synthetic data
- Exact aggregate pinning
- Domain-realistic distributions out of the box
- Schema inspection before generation
- Side-by-side: the same task in each tool
- Faker
- SDV
- Misata
- When to use each
- Installation
- Related
Faker vs SDV vs Misata: Choosing the Right Python Synthetic Data Tool
Generating synthetic data in Python means picking from three very different tools: Faker, SDV, and Misata. Each solves a distinct problem. This page shows the difference with working code so you can pick the right one in under five minutes.
Quick comparison#
| Feature | Faker | SDV | Misata |
|---|---|---|---|
| Multi-table relational output | ✗ | ✓ (limited) | ✓ |
| Referential integrity (FK) | Manual | ✓ | ✓ |
| Plain-English story → schema | ✗ | ✗ | ✓ |
| Exact aggregate targets (MRR, fraud rate) | ✗ | ✗ | ✓ |
| No real data required | ✓ | ✗ (needs training data) | ✓ |
| Domain-realistic distributions | Partial | ✓ | ✓ |
| Database seeding (SQLAlchemy) | Manual | ✗ | ✓ |
| Outcome curves / scenario events | ✗ | ✗ | ✓ |
| LLM-powered schema generation | ✗ | ✗ | ✓ |
| Reproducible seed | ✓ | ✓ | ✓ |
| Install size | Small | Large (~1 GB w/ deps) | Medium |
Faker: row-level fake data, no relationships#
Faker is excellent for generating standalone fake values. It has hundreds of providers covering names, addresses, credit card numbers, and more.
from faker import Faker
fake = Faker()
# One row of user data — fast and simple
print(fake.name()) # "Patricia Mueller"
print(fake.email()) # "tricia23@example.net"
print(fake.credit_card_number()) # "4532015112830366"Where Faker falls short:
# Building a customers → orders relationship by hand
customers = [{"id": i, "email": fake.email()} for i in range(1000)]
# You must manually wire FK integrity yourself — nothing enforces it
orders = [
{
"order_id": j,
"customer_id": random.randint(0, 999), # could reference a non-existent id
"amount": round(random.uniform(10, 500), 2),
}
for j in range(5000)
]There is no referential integrity, no distribution control, and no concept of business constraints. Every relationship you need, you build by hand.
Use Faker when: you need fake names, addresses, or values for a single table in a test fixture or form demo.
SDV (Synthetic Data Vault): statistical models from real data#
SDV learns statistical patterns from your actual data and generates synthetic rows that match those patterns. It handles single tables, conditional sampling, and some relational modeling.
from sdv.tabular import GaussianCopula
# SDV requires your real data to fit a model
model = GaussianCopula()
model.fit(real_customers_df) # ← needs real data
synthetic = model.sample(num_rows=1000)Where SDV falls short:
- You must have real data to train from, no real data, no model.
- Exact business rules ("fraud rate must be exactly 2%") cannot be pinned.
- Install pulls in PyTorch, CUDA libs, and ~1 GB of dependencies.
- Multi-table relational support is limited; deep FK chains require significant configuration.
Use SDV when: you have real data you cannot use directly (privacy concerns), and you want statistically faithful synthetic copies of it.
Misata: story-driven relational synthetic data#
Misata generates multi-table datasets from plain-English descriptions. No real data required. Referential integrity is guaranteed. Business constraints (fraud rates, MRR targets, churn percentages) are pinned exactly.
import misata
# One line — no real data, no model training, no FK wiring
tables = misata.generate("A fintech company with 2000 customers and banking transactions.", seed=42)
customers = tables["customers"] # 2,000 rows
accounts = tables["accounts"] # ~4,000 rows
transactions = tables["transactions"] # ~20,000 rows
# Referential integrity is automatic
assert (~transactions["account_id"].isin(accounts["account_id"])).sum() == 0Exact aggregate pinning#
schema = misata.parse(
"A SaaS company with 1000 users. "
"MRR rises from $50k in January to $200k in December with a dip in September.",
rows=1000,
)
tables = misata.generate_from_schema(schema)
# Monthly MRR sums match the targets exactly — not approximately
monthly = tables["subscriptions"].groupby(
pd.to_datetime(tables["subscriptions"]["start_date"]).dt.month
)["mrr"].sum()
# Jan → $50,000.00 ✓
# Dec → $200,000.00 ✓
# All 12 months hit their targets to the centDomain-realistic distributions out of the box#
Misata ships calibrated priors for 7 domains. Healthcare blood-type frequencies match real ABO/Rh statistics. Credit scores match real FICO distributions. MRR follows a log-normal curve because real SaaS revenue is log-normal.
# Healthcare — blood types follow real ABO/Rh frequencies (no config needed)
tables = misata.generate("A hospital with 500 patients and doctors.", seed=42)
patients = tables["patients"]
# O+: 38% real-world → ~38% generated ✓
# A+: 34% real-world → ~34% generated ✓
print(patients["blood_type"].value_counts(normalize=True).mul(100).round(1))Schema inspection before generation#
schema = misata.parse("An ecommerce store with 10k orders")
print(schema.summary())
# Schema: EcommerceDataset
# Domain: ecommerce
# Tables (2)
# customers 10,000 rows [customer_id, email, name, signup_date]
# orders 60,000 rows [order_id, customer_id, order_date, amount, status]
# Relationships (1)
# customers.customer_id → orders.customer_idUse Misata when: you need relational multi-table data with business logic, exact aggregate targets, or domain-realistic distributions, and you don't want to write schema config by hand.
Side-by-side: the same task in each tool#
Task: Generate 1,000 customers and 5,000 orders with referential integrity, where order amounts follow a realistic right-skewed distribution.
Faker#
import random
from faker import Faker
fake = Faker()
ids = list(range(1, 1001))
valid = set(ids)
customers = [{"customer_id": i, "email": fake.email(), "name": fake.name()} for i in ids]
# Manual FK wiring — you're responsible for this
orders = [
{
"order_id": j,
"customer_id": random.choice(ids), # could be any id
"amount": round(random.lognormvariate(4, 0.8), 2), # lognormal by hand
"order_date": fake.date_between("-2y", "today"),
}
for j in range(1, 5001)
]
# No integrity guarantee — you'd need to assert this yourself~30 lines. You wrote the distribution, the FK logic, and the date range manually.
SDV#
# SDV requires real data — this task cannot be completed from scratch.
# You would need real customer and order tables to fit the model first.Not applicable without a real dataset to train from.
Misata#
import misata
tables = misata.generate("An ecommerce store with 1000 customers and orders.", seed=42)
customers = tables["customers"] # 1,000 rows, realistic email/name/signup_date
orders = tables["orders"] # 5,000+ rows, right-skewed amounts
# FK integrity is guaranteed — no assertion needed, but it holds:
assert (~orders["customer_id"].isin(customers["customer_id"])).sum() == 03 lines. Referential integrity, realistic distributions, and reproducibility included.
When to use each#
| You want to… | Use |
|---|---|
| Generate fake names/emails/addresses for a form or test fixture | Faker |
| Create a privacy-safe copy of your production database | SDV |
| Build a realistic multi-table dataset from scratch (no real data) | Misata |
| Seed a test database with consistent relational data | Misata |
| Pin exact KPIs (fraud rate, churn %, MRR targets) in generated data | Misata |
| Generate data for BI demos, load testing, or ML training | Misata |
| Learn distributions from existing data | SDV |
| One-off fake value in a script | Faker |
Installation#
# Faker
pip install faker
# SDV (large, requires Python 3.8–3.11)
pip install sdv
# Misata (no real data required)
pip install misata
# Misata with LLM-powered schema generation
pip install "misata[llm]"