Synthetic Mobility Data Generation
Synthetic mobility data generation produces realistic-but-artificial trajectory datasets by fitting a generative model to real movement and then sampling entirely new agents — preserving aggregate statistics such as trip-length and radius-of-gyration distributions while breaking the one-to-one link between any published trace and a real person.
When to Generate Synthetic Data vs. Anonymize Real Traces permalink
Synthetic generation is the right tool when downstream users need realistic movement structure — plausible routines, hotspots, flows — but do not need any specific individual’s real path. When users must retain real records (for billing reconciliation, incident replay, or ground-truth evaluation), perturbing the originals with trajectory anonymization techniques is more appropriate. The pipeline below shows the decision.
Algorithmic Specification permalink
Three model families dominate practical mobility synthesis: discrete Markov chains over a tessellation, the exploration-and-preferential-return (EPR) family, and differentially private variants of either.
Markov transition model permalink
A first-order Markov model treats each visited location (a cell of a spatial tessellation) as a state and estimates the probability of moving to next as the normalized transition count:
where is the number of observed transitions from cell to cell across the whole population. Sampling a synthetic sequence is then a walk on this chain. Because a raw count of encodes exactly one person’s move, transition estimation must be smoothed — the mechanics of that smoothing, plus stationary-distribution analysis, are covered in the deep-dive on generating synthetic GPS traces with Markov models.
Exploration and preferential return (EPR) permalink
The EPR model reproduces the empirical law that people mostly revisit familiar places but occasionally explore new ones. If an agent has visited distinct locations, the probability that its next move is to a new location is:
With probability the agent instead returns to a previously visited location , chosen by preferential return — proportional to how often it has been there:
Jump lengths and waiting times follow heavy-tailed distributions:
Radius of gyration permalink
The primary utility invariant is the radius of gyration, which summarizes the spatial spread of an individual’s trajectory around its center of mass :
A synthetic dataset is only useful if the distribution of across synthetic agents matches the real population.
Parameter reference permalink
| Parameter | Symbol | Typical value | Role |
|---|---|---|---|
| Exploration coefficient | 0.6 | Scales the new-location probability | |
| Exploration exponent | 0.21 | Decay of exploration as grows | |
| Jump-length exponent | 0.75 | Heaviness of trip-length tail | |
| Waiting-time exponent | 0.8 | Heaviness of stop-duration tail | |
| Markov order | 1 | Memory depth of the diary/transition model | |
| Tessellation resolution | — | 500 m – 2 km cells | Spatial granularity of states |
| Smoothing strength | 0.5 – 1.0 | Laplace pseudocount on transitions |
Prerequisites & Data Requirements permalink
Before fitting a generator, confirm the following:
- Input trace schema: a long-format table with columns
uid,datetime,lat,lng(WGS84 / EPSG:4326, the CRS scikit-mobility expects). Reproject to a metric CRS only for distance measurements, not for the model itself. - Stop detection and clustering: raw GPS pings must be reduced to stops and clustered into stable locations, producing a
cluster(orlocation) column that becomes the Markov/EPR state space. - Spatial tessellation: a
GeoDataFrameof polygons (a uniform grid, H3 hexagons, or administrative units) with arelevancecolumn — usually population or historical visit counts — that seeds the density-EPR location choice. - Minimum dataset size: enough users that no diary state or transition is supported by a single individual. Fewer than a few hundred users invites memorization; prune or smooth any state with support below a threshold (e.g. fewer than contributing users).
- Dependencies:
scikit-mobility(skmob) for the EPR and Markov diary generators,numpy,pandas,geopandas, andh3if you build a hexagonal tessellation. No network calls are required at generation time.
Step-by-Step Implementation permalink
The reference pipeline uses scikit-mobility’s DITRAS construction: a MarkovDiaryGenerator for when and what, driving a density-based EPR model for where.
Step 1 — Preprocess Real Traces into Stops and States permalink
import skmob
from skmob.preprocessing import detection, clustering
# Real, consented GPS pings in WGS84 (EPSG:4326) — the CRS skmob assumes.
tdf = skmob.TrajDataFrame(
raw_df, # columns: uid, datetime, lat, lng
latitude="lat",
longitude="lng",
datetime="datetime",
user_id="uid",
)
# Reduce noisy pings to stationary stops (>= 20 min within a 200 m radius),
# then cluster stops into stable locations that become the model's states.
stops = detection.stay_locations(
tdf, stop_radius_factor=0.5, minutes_for_a_stop=20.0, spatial_radius_km=0.2
)
stops = clustering.cluster(stops, cluster_radius_km=0.1, min_samples=1)
# `stops` now carries a `cluster` column: the discrete location alphabet.
Working from stops rather than raw pings is a privacy decision as much as a modeling one: it discards the high-frequency detail that makes a raw trace uniquely identifiable, a risk quantified in re-identification risk assessment for geospatial datasets.
Step 2 — Fit the Markov Diary Generator permalink
from skmob.models.markov_diary_generator import MarkovDiaryGenerator
# The diary generator learns the population-level routine: the probability of
# being "at home", "at another frequent location", or "exploring" as a function
# of the hour and the previous state. It aggregates over ALL users, so no single
# person's schedule is memorized — provided support per state stays high.
mdg = MarkovDiaryGenerator()
mdg.fit(stops, n_individuals=stops["uid"].nunique(), lid="cluster")
The diary is intentionally coarse — a handful of abstract states over hourly bins — which limits how much any individual can influence the fitted matrix. Verify that every diary state is supported by many users before trusting it.
Step 3 — Build a Spatial Tessellation with Relevance permalink
import geopandas as gpd
# A tessellation of the study area. `relevance` (e.g. residential population)
# biases the EPR exploration step toward plausible destinations rather than
# revealing which specific cells the real cohort actually visited.
tessellation: gpd.GeoDataFrame = gpd.read_file("study_area_grid.geojson")
tessellation = tessellation.to_crs(epsg=4326) # skmob expects WGS84
tessellation = tessellation.rename(columns={"pop": "relevance"})
assert (tessellation["relevance"] >= 0).all()
Step 4 — Sample Synthetic Agents permalink
import pandas as pd
from skmob.models.epr import Ditras
# DITRAS = Markov diary (timing) + density-EPR (location choice).
# Each generated agent is a fresh random walk; it does not copy any real uid.
ditras = Ditras(diary_generator=mdg)
synth_tdf = ditras.generate(
start_date=pd.to_datetime("2026-01-06 00:00:00"),
end_date=pd.to_datetime("2026-01-13 00:00:00"),
spatial_tessellation=tessellation,
relevance_column="relevance",
n_agents=2000, # synthetic population size is your choice
random_state=42,
show_progress=True,
)
# synth_tdf has the same schema (uid, datetime, lat, lng) but artificial uids.
Synthetic uids are freshly minted integers with no mapping back to real users. That severed linkage is the intended privacy property — but it is not automatic, which is why Step 5 is mandatory rather than optional.
Step 5 — Validate Before Release permalink
from skmob.measures.individual import radius_of_gyration, jump_lengths
real_rg = radius_of_gyration(stops).set_index("uid")["radius_of_gyration"]
synth_rg = radius_of_gyration(synth_tdf).set_index("uid")["radius_of_gyration"]
# Compare distributions, never individuals: a good synthetic set matches the
# SHAPE of these curves without reproducing any one real trajectory.
print("real r_g median:", real_rg.median())
print("synth r_g median:", synth_rg.median())
Validation & Re-identification Testing permalink
Utility and privacy are validated separately — a synthetic set can pass one and fail the other.
Distributional utility checks permalink
Compare the real and synthetic populations on the invariants that mobility models are supposed to preserve, using a two-sample statistic such as the Kolmogorov–Smirnov distance:
import numpy as np
from scipy.stats import ks_2samp
from skmob.measures.individual import jump_lengths
def distributional_report(real_tdf, synth_tdf) -> dict:
"""KS distance between real and synthetic distributions (lower = better)."""
real_jl = np.concatenate(jump_lengths(real_tdf)["jump_lengths"].dropna().values)
synth_jl = np.concatenate(jump_lengths(synth_tdf)["jump_lengths"].dropna().values)
return {
"jump_length_ks": ks_2samp(real_jl, synth_jl).statistic,
# Repeat for radius_of_gyration, distinct-locations, and OD-flow cells.
}
Also compare the origin–destination (OD) flow matrix: aggregate transitions between tessellation cells for both sets and check that the cell-to-cell flow volumes correlate strongly. A synthetic set that reproduces jump lengths but scrambles OD flows is not useful for transport planning.
Membership-inference and uniqueness checks permalink
The critical privacy test asks whether the synthetic release reveals who was in the training data. Compute, for each real trajectory, the distance to its nearest synthetic trajectory; if some real traces have a synthetic near-duplicate far closer than they have real neighbors, the model has memorized them.
from scipy.spatial import cKDTree
import numpy as np
def nearest_synthetic_distance(real_pts: np.ndarray, synth_pts: np.ndarray) -> np.ndarray:
"""Min distance (deg) from each real location to the synthetic cloud.
Near-zero distances flag potential memorization / rare-trip leakage."""
tree = cKDTree(synth_pts)
d, _ = tree.query(real_pts, k=1)
return d
# A memorization alarm: real points with an implausibly exact synthetic match.
leak_fraction = np.mean(nearest_synthetic_distance(real_xy, synth_xy) < 1e-4)
print(f"Suspected memorized points: {leak_fraction:.4%}") # target: ~0
The uniqueness of the real traces sets the risk ceiling; estimating it directly is covered in estimating uniqueness of mobility traces. If real traces are highly unique, even a well-behaved generator can echo them, and you should move to a differentially private training procedure.
Common Failure Modes & Gotchas permalink
Overfitting reproduces real traces. High-order Markov models or EPR agents seeded from real visit histories can regenerate near-verbatim real paths. Keep the model order low, aggregate transitions across the whole cohort, and always run the nearest-synthetic-distance check above.
Rare-trip leakage. A trip taken by exactly one person (a visit to a specialist clinic, a remote address) survives naive fitting because its transition count is nonzero only for that individual. Prune or Laplace-smooth any transition or diary state with single-user support before sampling.
Sparse tessellation cells memorize. If a cell is visited by one user, EPR relevance and Markov transitions for that cell encode that user. Enforce a minimum contributing-user count per state.
Mismatched CRS in distance measures. scikit-mobility models operate in WGS84 degrees, but jump-length and radius-of-gyration comparisons must be computed with a geodesic or projected metric distance. Mixing degree and metre units silently corrupts every utility metric.
Treating “synthetic” as “anonymous” by definition. Synthetic provenance is not a legal safe harbour. Without the membership-inference test — and, for a formal claim, a differentially private fit — a synthetic release can carry the same disclosure risk as the raw data.
Compliance Alignment permalink
| Control | Satisfied by |
|---|---|
| GDPR Recital 26 — anonymous data | Synthetic records are not personal data only if re-identification is not reasonably likely; the membership-inference test is your evidence |
| GDPR Art. 25 — data protection by design | Fitting on aggregated stops and smoothing single-user states are architectural controls, not post-hoc fixes |
| GDPR Art. 35 — DPIA | Document the generator, smoothing parameters, and validation metrics as the impact assessment for the release |
| CCPA/CPRA “de-identified” standard | Requires a documented process preventing re-identification — the uniqueness and leakage checks supply it |
| NIST SP 800-188 | Synthetic data is listed as a disclosure-limitation technique conditional on a measured re-identification test |
For a formal, provable guarantee, train the transition and diary models under a privacy budget (epsilon) for spatial queries: add calibrated noise to the transition counts so the fitted model — and therefore every sampled agent — inherits epsilon-differential privacy. This is the “Yes” branch of the pipeline diagram, and it converts an empirical privacy argument into a mathematical one.
FAQ permalink
Is synthetic mobility data automatically anonymous?
No. A model that overfits can reproduce real trajectories almost verbatim, and rare trips can leak the individuals who took them. Synthetic data is private only if you constrain the model (low order, smoothed transitions, pruned single-user states) and confirm it with a membership-inference test. A formal guarantee requires a differentially private training procedure with a bounded epsilon.
How much real data do I need to fit a mobility generator?
Enough that no transition or diary state is supported by a single individual — in practice at least a few hundred users and several thousand stop events. Any state that only one person contributes to must be smoothed or pruned so it cannot be memorized.
What is the difference between the Markov and EPR models?
The Markov diary model governs when an agent moves and which activity it performs; the EPR model governs where it goes, trading off preferential return to familiar places against exploration of new ones. Combined models like DITRAS use the diary for timing and EPR for location.
Which distributions should I compare to validate utility?
Trip-length (jump-length) distribution, radius of gyration, number of distinct locations visited, and the origin–destination flow matrix, compared real vs. synthetic with a two-sample statistic such as the KS distance.
Related permalink
- Trajectory Anonymization Techniques — perturb real traces when users must keep the original records
- Generating Synthetic GPS Traces with Markov Models — transition-matrix estimation and smoothing, step by step
- Re-identification Risk Assessment for Geospatial Datasets — set the risk ceiling before you generate
- Privacy Budget Allocation for Spatial Queries — spend epsilon to make a DP-synthetic release provable
- Estimating Uniqueness of Mobility Traces — measure how memorable the real traces are