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.

Map of the Atlantic with New York's restaurants as a single point and a red point at 0 degrees latitude and longitude in the Gulf of Guinea, where 450 restaurants are plotted. Map of the Atlantic with New York's restaurants as a single point and a red point at 0 degrees latitude and longitude in the Gulf of Guinea, where 450 restaurants are plotted.
The extent of a map that keeps the zero coordinates.

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.

Stacked horizontal bars by borough of restaurants with latitude 0 and with no latitude, largest in Manhattan and Queens. Stacked horizontal bars by borough of restaurants with latitude 0 and with no latitude, largest in Manhattan and Queens.
Restaurants without a usable position, by borough: 810 in all.

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.

Bars comparing 810 restaurants with a zero or missing position against those with a BBL tax-lot number (450) and with a BIN building number (8). Bars comparing 810 restaurants with a zero or missing position against those with a BBL tax-lot number (450) and with a BIN building number (8).
More than half of the unplaced restaurants have a BBL that can place them.

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.

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.