Restaurants Plotted in the Ocean: Zero Coordinates in Inspection Data
On this page
Problem statement
Plot New York's restaurant inspection data and a few hundred points appear off the coast of West Africa, at latitude 0 and longitude 0. They are restaurants whose address could not be geocoded, given zero instead of an empty value. A further 360 restaurants have no position at all. Zeros stretch every map extent, fall outside every neighbourhood in a spatial join and drag any average position toward the equator. This page finds them, shows why they happen, and what to use instead.
Quick answer
Replace Latitude and Longitude values of 0 with missing before mapping or joining. For restaurants that matter, place them by BBL (the tax lot) or by geocoding the address; otherwise report how many were dropped.
r = insp.drop_duplicates("CAMIS")
print(f"restaurants: {len(r):,}; latitude 0: {(r.lat == 0).sum()}; latitude missing: {r.lat.isna().sum()}")
print(f"bounding box with zeros: lat {r.lat.min()} to {r.lat.max():.3f}; without: lat {r.lat[r.lat > 0].min():.3f} to {r.lat.max():.3f}")
restaurants: 31,531; latitude 0: 450; latitude missing: 360
bounding box with zeros: lat 0.0 to 40.913; without: lat 40.500 to 40.913
Of 31,531 restaurants, 450 have latitude 0 and 360 none. With the zeros, the latitude range starts at 0.0; without them, it is the city's 40.500 to 40.913.
Who has zero, and what it breaks
1. Who has zero
z = r[r.lat == 0]
print(z.BORO.value_counts().to_dict())
print(f"of those, ZIP code missing: {z.ZIPCODE.isna().mean():.1%}; building number present: {z.BUILDING.notna().mean():.1%}")
print(z[["DBA", "BUILDING", "STREET", "ZIPCODE"]].head(3).to_string(index=False))
{'Manhattan': 238, 'Queens': 129, 'Brooklyn': 36, 'Bronx': 34, 'Staten Island': 13}
of those, ZIP code missing: 100.0%; building number present: 100.0%
DBA BUILDING STREET ZIPCODE
STARBUCKS 0 GRAND CENTRAL STATION TERMINAL NaN
ATLIXCO BAKERY & DELI 1012 ST PAULS AVE NaN
375 RESERVE 14 S SERVICE RD NaN
The zeros are mostly in Manhattan (238) and Queens (129). Every one is missing its ZIP code, while all have a building number and street, among them a Starbucks in Grand Central Terminal with building number 0. Without a ZIP code the geocoder could not resolve the address.
2. What zero does to a map and a join
pts = gpd.GeoDataFrame(r.dropna(subset=["lat"]), geometry=gpd.points_from_xy(r.dropna(subset=["lat"]).lon, r.dropna(subset=["lat"]).lat), crs=4326).to_crs(2263)
j = gpd.sjoin(pts, nta[["NTA2020", "geometry"]], predicate="within", how="left")
print(f"points inside an NTA: {j.NTA2020.notna().mean():.1%}; zero points inside: {j[j.lat == 0].NTA2020.notna().sum()}")
points inside an NTA: 98.5%; zero points inside: 0
In a spatial join to 2020 neighbourhoods, 98.5% of restaurants with any position fall inside an NTA, and not one of the zero points does. They drop out of every neighbourhood count without an error, and on a map they pull the extent to the equator.
3. Treat zero as missing
r = r.assign(lat=r.lat.replace(0, np.nan), lon=r.lon.replace(0, np.nan))
print(f"without a usable position: {r.lat.isna().sum()} ({r.lat.isna().mean():.1%}); usable: {r.lat.notna().sum():,}")
without a usable position: 810 (2.6%); usable: 30,721
Treated as missing, 810 restaurants (2.6%) have no usable position and 30,721 do. That number belongs in any map caption.
Other ways to place them
The data used on this page
New York City Department of Health and Mental Hygiene restaurant inspection results from NYC Open Data, saved on this site on 16 October 2026, and the city's 2020 Neighborhood Tabulation Areas.
from pathlib import Path
import geopandas as gpd
import numpy as np
import pandas as pd
import requests
from shapely import wkt
Path("data").mkdir(exist_ok=True)
# NYC Department of Health and Mental Hygiene (DOHMH) restaurant inspection results (NYC Open Data, dataset 43nn-pn8j),
# downloaded on 16 October 2026 and saved on this site (xz-compressed, unchanged), because the city replaces it daily.
# Live: https://data.cityofnewyork.us/api/views/43nn-pn8j/rows.csv?accessType=DOWNLOAD
# Also NYC 2020 Neighborhood Tabulation Areas (dataset 9nt8-h7nd), saved on 15 October 2026.
FILES = ["nyc-restaurant-inspections-2026-10-16-v2.csv.xz", "nyc-nta-2020-2026-10-15.zip"]
for name in FILES:
if not Path(f"data/{name}").exists():
Path(f"data/{name}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{name}", timeout=600).content)
insp = pd.read_csv("data/nyc-restaurant-inspections-2026-10-16-v2.csv.xz", dtype=str)
insp["date"] = pd.to_datetime(insp["INSPECTION DATE"], format="%m/%d/%Y")
insp["SCORE"] = pd.to_numeric(insp.SCORE)
insp["lat"], insp["lon"] = pd.to_numeric(insp.Latitude), pd.to_numeric(insp.Longitude)
n = pd.read_csv("data/nyc-nta-2020-2026-10-15.zip")
nta = gpd.GeoDataFrame(n.drop(columns="the_geom"), geometry=n.the_geom.map(wkt.loads), crs=4326).to_crs(2263)
print(f"DOHMH inspections file: {len(insp):,} rows for {insp.CAMIS.nunique():,} restaurants")
DOHMH inspections file: 296,514 rows for 31,531 restaurants
Example 1: place the rest by BBL or BIN
z2 = insp.drop_duplicates("CAMIS")
z2 = z2[(z2.lat == 0) | z2.lat.isna()]
print(f"zero or missing positions with a BBL: {z2.BBL.notna().mean():.1%}; with a BIN: {z2.BIN.notna().mean():.1%}")
zero or missing positions with a BBL: 55.6%; with a BIN: 1.0%
55.6% of the unplaced restaurants have a BBL, the borough-block-lot of their tax lot, which can be joined to the city's MapPLUTO lots for a position. Only 1.0% have a BIN, a building number.
Example 2: boroughs coded 0
print(f"rows with BORO '0': {(insp.BORO == '0').sum()}; their latitude is missing: {insp[insp.BORO == '0'].lat.isna().mean():.1%}")
rows with BORO '0': 339; their latitude is missing: 100.0%
The 339 rows with borough 0 have no position at all: missing, not zero. A cleaning step needs both checks.
Why zero instead of blank
Latitude and longitude are filled by geocoding each restaurant's address. When the geocoder fails, here mostly because the ZIP code is missing, the export writes 0 for both, a common default for numeric fields that cannot be null. Zero is a valid coordinate, a point in the Gulf of Guinea called Null Island, so nothing downstream flags it. Any coordinate pair of exactly (0, 0) in data about a city should be treated as missing.
Zero-coordinate checks
- Check the bounding box of every point layer before using it. Here the minimum latitude of 0 gives it away.
- Missing and zero are different. 360 restaurants have no position and 450 have zero; handle both.
- Borough 0 rows have no position at all.
- Do not jitter or impute positions for a published map; leave the restaurants off and say how many.
Internal links
- How to analyse NYC restaurant inspection results β the file and its fields
- How to map restaurant inspection scores by neighbourhood β scores on a map
- Neighbourhood codes that match no boundary β 2010 against 2020 NTAs
FAQ
Why are some NYC restaurants at latitude 0 longitude 0?
Their address could not be geocoded, mostly because the ZIP code is missing, and the export wrote 0 for both coordinates. 450 restaurants in the October 2026 file have zero coordinates.
How do I remove zero coordinates in pandas?
df[["lat", "lon"]] = df[["lat", "lon"]].replace(0, np.nan), then drop or separately handle the missing values.