Find Nearby ZIP Codes in R with zipcodeR

R Published Updated 3 mins read By Leon Wei
Find Nearby ZIP Codes in R with zipcodeR cover image

Quick summary

Summarize this blog with AI

Introduction

The zipcodeR package does not provide the getzips API shown in the old version of this guide. For new work, zipcodeR 0.4.1 offers search_radius_ng() with an explicitly selected data bundle. It finds stored ZIP reference points within a radius; it does not geocode a street address, calculate driving distance, or test polygon intersection.

Tested with R 4.5.1, zipcodeR 0.4.1 and data bundle 2026.09. Run the code blocks in order. Install the named packages before starting; inputs are supplied below.

Select an explicit data release

Install zipcodeR before starting. This setup downloads the registered 2026.09 bundle on its first use, verifies its checksum, and subsequently uses the cache. Lookup calls themselves do not download data.

bundle <- zipcodeR::download_zip_data_bundle("2026.09")
stopifnot(bundle$metadata$data_version == "2026.09")
print(bundle$metadata$data_version)  # "2026.09"
stopifnot(!"getzips" %in% getNamespaceExports("zipcodeR"))
provenance <- zipcodeR::zip_data_provenance(bundle)
print(provenance[provenance$field == "coordinates_and_area", c("source_id", "method")])
# gazetteer_zcta; Census ZCTA internal points and area

The bundle version is not a claim that every source field was collected in September 2026. Its coordinate source includes the 2024 Census ZCTA Gazetteer; other fields have other vintages. Inspect provenance before treating a coordinate as current. The package reference distinguishes this explicit-bundle API from its historical compatibility interface.

Search by latitude, longitude and miles

The coordinate is in decimal degrees, latitude first in the named arguments. Longitude is negative west of Greenwich. The radius and returned distance are in miles.

nearby <- zipcodeR::search_radius_ng(bundle, lat = 40.785091, lng = -73.968285, radius = 1)
print(data.frame(zipcode = nearby$zipcode, miles = round(nearby$distance, 3)))
# 10024 0.161
# 10025 0.909
# 10023 0.919
# 10128 0.978
# 10028 0.982
stopifnot(identical(nearby$zipcode, c("10024", "10025", "10023", "10128", "10028")))
stopifnot(all(nearby$distance <= 1), all(diff(nearby$distance) >= 0))

These are exact IDs for the pinned fixture, with distances rounded for display. The spherical reference-point distance is not road distance. A ZIP whose point falls outside the circle may still cover addresses inside it. ZIP codes are postal identifiers; Census ZCTAs are statistical approximations, not identical delivery boundaries.

Start from a ZIP without losing leading zeros

Keep ZIP identifiers as character strings. A ZIP-based search uses its stored reference point, not the user’s exact location.

center <- zipcodeR::geocode_zip_ng(bundle, "10024")
stopifnot(nrow(center) == 1L, is.finite(center$lat), is.finite(center$lng))
from_zip <- zipcodeR::search_radius_ng(bundle, lat = center$lat, lng = center$lng, radius = 1)
stopifnot("10024" %in% from_zip$zipcode)
stopifnot(from_zip$distance[from_zip$zipcode == "10024"] < 1e-8)
points <- data.frame(lat = c(40.785091, center$lat), lng = c(-73.968285, center$lng))
results <- lapply(seq_len(nrow(points)), function(i)
  zipcodeR::search_radius_ng(bundle, lat = points$lat[i], lng = points$lng[i], radius = 1))
stopifnot(length(results) == 2L)

Use a list because locations can return different numbers of ZIPs. Do not force unequal result lengths into one column without defining a row-per-location-per-ZIP structure. Verify that geocoding returns finite coordinates before starting a search.

Handle invalid coordinates and zero matches

An empty result is valid and different from invalid input. A zero-radius search includes only reference points at the supplied location.

no_match <- suppressWarnings(zipcodeR::search_radius_ng(bundle, lat = 0, lng = 0, radius = 1))
stopifnot(nrow(no_match) == 0L, is.character(no_match$zipcode))
for (lat in list(NA_real_, 91, c(40, 41), "40"))
  stopifnot(inherits(tryCatch(zipcodeR::search_radius_ng(bundle, lat, -73, 1),
                           error = identity), "error"))
stopifnot(inherits(tryCatch(zipcodeR::search_radius_ng(bundle, 40, -73, -1),
                          error = identity), "error"))

The empty-result warning is intentionally captured in this known test. In an application, show “no reference points within the selected radius” and retain the query and bundle version. Increasing the radius changes the question rather than proving the first answer wrong.

Primary references

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles