Fixing Airbnb Listing Coordinates That Are Not Exact

On this page

Problem statement

You plot Airbnb listings over a street map and many sit in the middle of roads, in parks or in rivers, listings you know are in one apartment building are scattered across a block, and a spatial join to small areas gives results that change when you look closely. Nothing is wrong with your code. Airbnb deliberately shifts each listing's published location by up to about 150 metres to protect hosts' privacy, and Inside Airbnb's data inherits those shifted coordinates.

This page measures the effect in the Inside Airbnb snapshot for Washington, D.C. of 24 June 2026. It checks how many listing points fall inside a building footprint from OpenStreetMap, looks at the spread of listings that belong to the same large operator, and shows which analyses the blurring affects and which it does not.

Quick answer

Treat each listing as an area about 150 m across, not a point. Buffer before joining to small areas:

G = gpd.GeoDataFrame(L, geometry=gpd.points_from_xy(L.longitude, L.latitude), crs=4326).to_crs(32618)
inside = gpd.sjoin(G[["id", "geometry"]], B[["geometry"]], predicate="within", how="left").groupby(level=0).index_right.count() > 0
print(f"listing points inside a building footprint: {inside.mean():.0%} of {len(G):,}")
G["zone"] = G.buffer(150)                                    # where the listing could actually be
print(f"area of a 150 m uncertainty circle: {G.zone.iloc[0].area / 1e4:.1f} ha")
listing points inside a building footprint: 47% of 6,945
area of a 150 m uncertainty circle: 7.1 ha

Fewer than half the listing points, 47%, fall inside any building footprint. The other 53% sit in streets, yards, parks or other open ground. A 150 m circle around each point covers 7.1 hectares.

Close-up of 1.2 km of central D.C. with listing points over OSM building footprints and 150 m circles Close-up of 1.2 km of central D.C. with listing points over OSM building footprints and 150 m circles
In this dense block 321 of 415 points fall in a footprint; a 150 m circle covers dozens of buildings.

Expected building shares, one operator's points, the effect

1. What share of buildings would we expect?

Compare with random points inside the same area, to see whether listing points avoid buildings or fall in them:

rng = np.random.default_rng(0)
x0, y0, x1, y1 = G.total_bounds
pts = gpd.GeoSeries(gpd.points_from_xy(rng.uniform(x0, x1, 20000), rng.uniform(y0, y1, 20000)), crs=32618)
pts = pts[pts.within(DC.union_all())]
rnd = gpd.sjoin(gpd.GeoDataFrame(geometry=pts), B[["geometry"]], predicate="within", how="left").groupby(level=0).index_right.count() > 0
print(f"random points in D.C. inside a building: {rnd.mean():.0%}; listing points: {inside.mean():.0%}")
random points in D.C. inside a building: 15%; listing points: 47%

Random points across the District land in a building 15% of the time, so listing points do fall in buildings three times as often as chance. They are close to real homes but not on them, consistent with a shift of tens of metres rather than a random placement.

Share of points inside a building footprint: listings against random points. points inside a buildinglisting points47%random points in D.C.15%025%50%75%100%161,846 OSM footprintsNear homes, but often not on them spatialworkflow.io
47% of listing points against 15% of random points.

2. One operator, one building, many points

Hosts with many listings often run several units in one building. Check whether their points coincide or spread out, and how far apart the listings of smaller multi-listing hosts sit:

big = G[G.calculated_host_listings_count >= 50]
dup = G.duplicated(["latitude", "longitude"], keep=False)
print(f"hosts with 50+ listings: {big.host_id.nunique()}, holding {len(big):,} listings")
print(f"listings sharing exact coordinates with another listing: hosts with 50+ {dup[big.index].mean():.0%}, everyone else {dup.drop(big.index).mean():.0%}")
multi = G.drop(big.index); multi = multi[multi.host_id.duplicated(keep=False)]
nn = multi.groupby("host_id").apply(lambda g: g.geometry.apply(lambda p: g.distance(p).nsmallest(2).iloc[-1]), include_groups=False)
print(f"smaller multi-listing hosts: median distance to the host's nearest other listing {nn.median():.0f} m, over {nn.notna().sum():,} listings")
hosts with 50+ listings: 8, holding 952 listings
listings sharing exact coordinates with another listing: hosts with 50+ 63%, everyone else 12%
smaller multi-listing hosts: median distance to the host's nearest other listing 62 m, over 3,637 listings

The eight hosts with 50 or more listings behave differently from everyone else: 63% of their listings share exact coordinates with another listing, against 12% for the rest. For smaller hosts with more than one listing, the nearest other listing of the same host is a median 62 m away. That fits units in one building scattered by separate shifts, although it could also reflect hosts with units on neighbouring streets.

3. What it does to small-area counts

Assign listings to a fine grid and compare with a grid twice as coarse:

for size in (250, 500, 1000):
    cell = (G.geometry.x // size).astype(int).astype(str) + "_" + (G.geometry.y // size).astype(int).astype(str)
    shifted = ((G.geometry.x + 100) // size).astype(int).astype(str) + "_" + ((G.geometry.y + 100) // size).astype(int).astype(str)
    print(f"{size:>5} m cells: listings changing cell if moved 100 m north-east: {(cell != shifted).mean():.0%}")
  250 m cells: listings changing cell if moved 100 m north-east: 64%
  500 m cells: listings changing cell if moved 100 m north-east: 38%
 1000 m cells: listings changing cell if moved 100 m north-east: 19%

A 100 m shift moves 64% of listings into a different 250 m grid cell, 38% at 500 m and 19% at 1 km. Counts for areas a few hundred metres across depend heavily on where the points happened to land.

Buildings within the uncertainty circle

The data used on this page

The Inside Airbnb listings for Washington, D.C. from the snapshot of 24 June 2026 (CC BY 4.0); building footprints (building=* areas) from the Geofabrik OpenStreetMap extract of 1 January 2026 (Β© OpenStreetMap contributors, ODbL); and the District boundary from the US Census Bureau's 2023 cartographic boundaries at 1:500,000.

from pathlib import Path

import geopandas as gpd
import numpy as np
import osmium
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
def cached(path, url):
    p = Path(path)
    if not p.exists():
        r = requests.get(url, timeout=600); r.raise_for_status(); p.write_bytes(r.content)
    return p
L = pd.read_csv(cached("data/listings.csv.gz", "https://data.insideairbnb.com/united-states/dc/washington-dc/2026-06-24/data/listings.csv.gz"))
PBF = cached("data/district-of-columbia-260101.osm.pbf", "https://download.geofabrik.de/north-america/us/district-of-columbia-260101.osm.pbf")
fp = (osmium.FileProcessor(str(PBF)).with_areas().with_filter(osmium.filter.EntityFilter(osmium.osm.AREA))
      .with_filter(osmium.filter.KeyFilter("building")).with_filter(osmium.filter.GeoInterfaceFilter(tags=["building"])))
B = gpd.GeoDataFrame.from_features(fp, crs=4326).to_crs(32618)
C = gpd.read_file(cached("data/cb_2023_us_county_500k.zip", "https://www2.census.gov/geo/tiger/GENZ2023/shp/cb_2023_us_county_500k.zip"))
DC = C[C.GEOID == "11001"].to_crs(32618)
print(f"{len(L):,} listings; {len(B):,} building footprints")
6,945 listings; 161,846 building footprints

The OpenStreetMap extract has 161,846 building footprints for the District.

Example 1: buildings within the uncertainty circle

How many buildings could each listing actually be in?

sample = G.sample(500, random_state=0)
n_b = gpd.sjoin(gpd.GeoDataFrame(geometry=sample.zone, crs=32618), B[["geometry"]], predicate="intersects").groupby(level=0).size().reindex(sample.index, fill_value=0)
print(f"buildings within 150 m of a listing point: median {n_b.median():.0f}, middle half {n_b.quantile(0.25):.0f} to {n_b.quantile(0.75):.0f}")
buildings within 150 m of a listing point: median 157, middle half 89 to 218

A 150 m circle around a listing point contains a median of 157 buildings, and the middle half of listings have 89 to 218. Coordinates alone cannot say which of them is the listing.

Line of the share of listings changing grid cell after a 100 m shift, by cell size Line of the share of listings changing grid cell after a 100 m shift, by cell size
64% change cell at 250 m, 19% at 1 km.

Locations anonymised before booking

Which analyses blurred listing locations affect. safe and unsafe scaleslittle affectedβœ“city-wide mapsβœ“neighbourhood mediansβœ“kernel density over 150 mdominated by the blurβœ“listings per buildingβœ“grid cells of 250 m: 64% moveβœ“distances under 150 mLocations shifted up to about 150 m spatialworkflow.io
The blur matters at the scale of blocks and buildings.

Airbnb does not publish exact addresses before a booking. The map shows each listing at an approximate location, and Inside Airbnb, which scrapes those locations, documents that they are anonymised to within about 150 m of the real address. The shift appears to be applied per listing, so units in the same building usually appear at different points, although many listings of the largest operators share a single point, and points can land on roads, open space or water. For city-wide maps and neighbourhood statistics this hardly matters, because the error is small relative to the areas. For anything at the scale of a block or a building, such as matching listings to properties, counting listings per building or measuring distance to a nearby feature, it dominates.

Analyses should work at a scale above the blurring. Aggregate to areas several hundred metres across or more, buffer each point by the uncertainty before spatial joins, and treat distances under about 150 m as noise. Matching individual listings to buildings or registrations needs other information, such as licence numbers or descriptions, not coordinates. When reporting counts for small areas, say that locations are approximate.

Exact duplicates, density maps and enforcement

Checks when working with Airbnb listing locations. working with blurred pointsaggregate to areasneighbourhoods, not cellsbuffer before joins150 m, 7.1 hawatch exact duplicates63% for hosts with 50+no building-level claimsmedian 157 buildings in rangesay locations are approximatein every small-area figureInside Airbnb D.C., 24 June 2026 spatialworkflow.io
Large operators often share one point for many listings.
  • Exact duplicates: identical coordinates for several listings often mean one operator, not one unit.
  • Hexbin and kernel density: smooth over the uncertainty naturally; choose a bandwidth well above 150 m.
  • Distance measures: distances to stations or landmarks are biased slightly upward on average by random shifts.
  • Enforcement: cities matching listings to properties use platform data under agreements, not the public coordinates.

FAQ

Are Airbnb listing locations accurate?

No; Airbnb shifts published locations by up to about 150 m for privacy, and Inside Airbnb's data uses those shifted points.

Why are Airbnb listings shown in the street or in parks?

The random shift can move a listing's point off its building onto roads, parks or water.

How do I analyse Airbnb data at a small scale?

Aggregate to areas larger than the uncertainty, buffer points by about 150 m before joins, and avoid building-level conclusions.

Can I find the exact building of an Airbnb listing?

Not from the public coordinates; other evidence such as licence numbers is needed.