Geoprivacy explained: why coordinates are personal data

Problem statement

A name can be shared by thousands of people. A residential coordinate is shared by one household, and in a suburb it is shared by one household for its lifetime. That asymmetry is the whole of geoprivacy, and nothing in the Python stack signals it: gdf.to_file("cases.gpkg") writes a home address to eleven decimal places as readily as it writes a bus stop.

Three things make spatial columns behave differently from the rest of a table:

  • A coordinate is a near-unique key. In the OpenStreetMap address extract for Brighton & Hove used throughout this cluster, 3,109 address points produced 3,109 distinct coordinates at six decimal places. Every row was alone on its own location.
  • It is a key you cannot revoke. A person can change a phone number. They cannot change where they lived in 2019, and a released point stays joinable to every future address dataset.
  • It carries the attribute you were trying to hide. The reason a dataset is sensitive is usually the reason a point exists in it at all: a clinic visit, an eviction, a species nest, a refuge. Publishing "where" publishes "who" and "what" at once.

This guide sets out what makes spatial data personal, when it stops being personal, and which of the three defences โ€” aggregate, mask, add noise โ€” answers which threat.

Quick answer

Treat a coordinate as an identifier, and decide what it is joinable to before you decide how to release it:

import geopandas as gpd

cases = gpd.read_file("cases.gpkg")

# The three questions that decide everything else
print("rows:", len(cases))
print("distinct locations:", cases.geometry.apply(lambda g: (g.x, g.y)).nunique())
print("smallest group:", cases.groupby(["month", "category"]).size().min())

If distinct locations is close to the row count, every row is individually addressable. If the smallest published group is 1, the release identifies someone whatever the map looks like. On the UK police open crime extract for central Leicester (4,950 records, three months of 2025), the numbers were 490 distinct locations and a smallest group of 1 โ€” an aggregate that looks safe wrapped around a unit that is not.

Three panels showing a coordinate as a unique key, an unrevocable key, and a key that carries the sensitive attribute.
The geometry column is the identifier; the attributes only say what it identifies someone as.

Step-by-step solution

1. Decide whether the points are about people at all

Bus stops, gauging stations and mountain summits are not personal data, and treating them as if they were wastes accuracy nobody gains from. The test is not whether the dataset mentions a person; it is whether a point can be traced back to one. A point at a commercial address with forty tenants is weaker than a point at a detached house, and a point at a hospital is weaker still. Density of possible subjects at the location is the quantity that matters.

2. Write down what an attacker already has

Re-identification is always a join. Nobody recognises a dot; they join it to a gazetteer, an address file, an electoral roll or a property listing, all of which are cheap to obtain. Britain's Ordnance Survey AddressBase, the US National Address Database and OpenStreetMap all publish coordinates for residences. Re-identification risk in spatial data explained works through the joins in order of effort.

3. Identify the unit that will actually be published

A dataset is rarely released as one table. It is released as a map, a set of filters and a download, and the smallest cell a user can reach is the real unit of disclosure. The Leicester crime extract has a median of 5 crimes per snap point, which sounds adequate; split by month and category โ€” the filters the published map offers โ€” and 68.0% of the 2,760 resulting cells hold exactly one crime.

4. Choose between the three defences

They are not interchangeable:

  • Aggregate when you can accept losing the point geometry. It is the only defence that is provably safe once the threshold is met, and the only one that survives repeat releases.
  • Mask when the analysis needs points but not exact ones. Displacement buys plausible deniability, not proof, and it degrades under repetition.
  • Add calibrated noise when you must publish many overlapping counts. Differential privacy is the only defence with a formal guarantee across queries, and the only one that makes the noise budget explicit.

5. Measure what each one costs the analysis

Every defence moves the data. On the Brighton address points, a donut mask displacing points by 50โ€“300 m raised the mean nearest-neighbour distance from 11.8 m to 28.0 m and left only 43.1% of the top-5% kernel density cells in the top 5% afterwards. That may be acceptable; it is never free, and it must be stated in the metadata. Geomasking changed the result of the analysis measures the whole trade-off curve.

6. Record the decision with the data

The release is a claim about risk, and claims need evidence. Store the method, its parameters, the random seed if any, the threshold applied and the date, in the dataset's own metadata rather than in an email. How to write a metadata record for a dataset in Python covers where it goes.

Comparison grid of aggregation, geomasking and differential privacy across guarantee, repeat releases, geometry kept and analysis cost.
Only one of the three survives being run twice on the same people.

Code examples

Example 1 โ€” the three numbers that decide the release

import geopandas as gpd
import pandas as pd

def disclosure_profile(gdf, group_cols=()):
    xy = pd.Series(list(zip(gdf.geometry.x.round(6), gdf.geometry.y.round(6))))
    profile = {
        "rows": len(gdf),
        "distinct_locations": xy.nunique(),
        "rows_alone_on_a_location": int((xy.value_counts() == 1).sum()),
    }
    if group_cols:
        cells = gdf.groupby([xy, *[gdf[c] for c in group_cols]]).size()
        profile["published_cells"] = len(cells)
        profile["cells_of_one"] = int((cells == 1).sum())
    return profile

crimes = gpd.read_file("leicester_2025Q1.gpkg")
print(disclosure_profile(crimes, ["month", "category"]))
{'rows': 4950, 'distinct_locations': 490, 'rows_alone_on_a_location': 70,
 'published_cells': 2760, 'cells_of_one': 1878}

The dataset is published as an already-anonymised extract, and at the level of a bare point it is: 490 locations for 4,950 crimes. At the level users actually query it, 1,878 of 2,760 cells describe a single incident.

Example 2 โ€” how much a coordinate narrows the field

import numpy as np

# Degrees of latitude/longitude per decimal place, at Brighton's latitude
for dp in range(2, 7):
    lat_m = 10 ** (-dp) * 111_320
    lon_m = lat_m * np.cos(np.deg2rad(50.83))
    print(f"{dp} dp: {lat_m:8.2f} m x {lon_m:7.2f} m")
2 dp:  1113.20 m x  703.12 m
3 dp:   111.32 m x   70.31 m
4 dp:    11.13 m x    7.03 m
5 dp:     1.11 m x    0.70 m
6 dp:     0.11 m x    0.07 m

Four decimal places is a room. Three is a block of houses. Two is a neighbourhood. A file written with GeoPandas' defaults carries thirteen to fifteen.

Example 3 โ€” the point is a join key, not a label

from scipy.spatial import cKDTree
import numpy as np

# 'released' points, and a public address file an attacker can download
tree = cKDTree(np.c_[addresses.geometry.x, addresses.geometry.y])
dist, idx = tree.query(np.c_[released.geometry.x, released.geometry.y], k=1)

print(f"median distance to the nearest known address: {np.median(dist):.1f} m")
print(f"released points within 10 m of exactly one address: {(dist < 10).sum()}")

Run this against your own release before anyone else does. If most points sit on top of a single address, the release is a list of addresses with extra columns.

Explanation

Why "anonymised" is the wrong word for removing names

Dropping a name column removes a direct identifier. It does not remove the indirect ones, and location is the strongest indirect identifier in most datasets, because it is both unique and externally available. The vocabulary that matters is identifiers (unique by themselves), quasi-identifiers (unique in combination, which is where location lives) and sensitive attributes (the thing you are protecting). Geoprivacy work is the management of a quasi-identifier so strong it usually behaves like an identifier.

Why aggregation is different in kind

Masking and noise both keep a row per person, and the person's row is still there to be attacked. Aggregation destroys the row: after a group-by there is no per-person record to re-identify, only a count. This is why it survives repeated publication and the other two do not. Sixteen independent donut-masked releases of the same Brighton addresses put 59.0% of points within 50 m of the truth when averaged; sixteen releases of a count table give you the count sixteen times.

Why the law is not the interesting constraint

The GDPR's definition of personal data โ€” information relating to an identified or identifiable natural person โ€” already covers a coordinate that can be linked to a household, and most national statistics offices apply thresholds well beyond what the law requires. But legal compliance is a floor computed on the data you released, and the failures in this cluster are all about the data you released twice, or released alongside something else. Build the threat model, not the compliance document.

Why this is not only about people

Nest sites of trafficked species, archaeological finds, unexploded ordnance and safe houses all carry the same structure: a coordinate whose value to an attacker is exactly its precision. The methods here transfer unchanged; only the definition of "subject" changes.

Ladder of coordinate decimal places from two to six with the ground cell size and what it identifies at each level.
Every decimal place you keep divides the pool of possible households by about a hundred.
Decision tree asking whether a released point can be traced to one household, branching to publishing as is, masking, aggregating, or not publishing points at all.
Two of the four answers do not involve a masking radius at all.

Edge cases or notes

  • Aggregated data can still be personal. A count of one in a ward is a person, whatever the geometry says.
  • Small populations make everything harder. A rare condition, a small ethnic group or a sparsely populated region can be identifying at any geography.
  • Trajectories are worse than points. Four points are enough to single out 95% of people in a mobile-phone dataset; a track is dozens.
  • Derived surfaces leak. A kernel density raster at a fine bandwidth has a local maximum at every input point.
  • Vector map exports leak. An SVG or PDF of a "blurred" scatter contains one element per point with its exact coordinates.
  • The centroid of a small polygon is a point. Replacing coordinates with the centroid of a single-building parcel protects nothing.
  • Geocoding quality varies by group. Rooftop matches are commoner in wealthier areas, so precision โ€” and therefore risk โ€” is not evenly distributed.
  • Ask whether the points need publishing at all. Many analyses ship as maps and tables, and the point file was never the deliverable.

FAQ

Is a latitude and longitude personal data?

It is when it can be linked to an identifiable person, which a residential coordinate almost always can. Regulators treat household-level location as personal data, and the practical test is whether a public address file would match it.

Does removing names make a dataset anonymous?

No. Names are direct identifiers; location is a quasi-identifier strong enough to single out a household on its own. Every address point in the Brighton extract was unique at six decimal places.

How precise is too precise?

At mid-latitudes, four decimal places resolves to about 11 m by 7 m, which is a single building. Three resolves to roughly 111 m by 70 m, which is a block. Choose the precision from the smallest group you are willing to publish, not from what the sensor produced.

Is geomasking enough on its own?

Only against a single release and a casual attacker. It gives no formal guarantee, it can be averaged away across repeat releases, and it distorts the analysis in ways that must be reported.

What about aggregated maps โ€” are they always safe?

No. A choropleth of a rare event can have cells containing one person, and a set of overlapping aggregations can be subtracted to recover an individual cell.

Which defence should I start with?

Aggregation, because it is the only one that removes the individual record. Reach for masking or noise when the analysis genuinely needs points or many overlapping counts.