Synthetic Hospital Data

Fake NHS Trust: a glass hospital with floating table schemas and activity charts

A seeded, reproducible synthetic dataset for Fake NHS Trust, a fictional large acute trust with two hospitals, covering 1 April 2023 to 31 March 2026. It is built to look like the extracts an analyst actually receives: a patient administration system with ADT events, an emergency department tracking board, outpatient appointments and referral-to-treatment pathways, CT, MRI and ultrasound in a radiology system, and an endoscopy vendor export. The data is deliberately messy, every imperfection is catalogued and recorded in an answers table, and a derived layer rebuilds spells, episodes and national-return aggregates from the raw events so the difference between event data and aggregated data can be explored directly. No row describes a real person; every NHS number carries the fake_ prefix.

800,000patients
38 millionrows across 9 source tables
3 yearsApril 2023 to March 2026
2.4 millioncatalogued imperfections
68kinds of mess

Get the data

Browse the tables

An index of every table with row counts, sizes and download links, and the first 100 rows of each from the sample.

Entity relationship diagrams

One diagram per layer, generated from the data: every link is inferred from column names and then measured, so clean keys and messy cross-system identifiers are drawn differently with their match rates.

5% sample (in this site, 129 MB)

About 40,000 patients with all of their rows in every table, so joins hold. Parquet, with a CSV zip for tools that cannot read Parquet.

Full set (GitHub release, 1.1 GB)

Every row, one Parquet asset per file. Asset names are the paths under full/ with / replaced by __.

Query it from Python or R

DuckDB reads the Parquet straight from the URL.

import duckdb
base = "https://thomuk.github.io/synthetic-hospital-data/data/v2/sample/"
duckdb.sql(f"""
  SELECT tretspef_first, count(*) AS spells, median(speldur) AS median_los
  FROM read_parquet('{base}derived/spells.parquet')
  WHERE emergency GROUP BY 1 ORDER BY 2 DESC
""").show()
library(duckdb); con <- dbConnect(duckdb())
base <- "https://thomuk.github.io/synthetic-hospital-data/data/v2/sample/"
dbGetQuery(con, sprintf("SELECT count(*) FROM read_parquet('%sderived/spells.parquet')", base))

SQL console in the browser coming next

A DuckDB-WASM console pre-loaded with the sample, a schema browser, and an explainer walking from an ADT event to a spell to a midnight census with runnable queries.

Read about it