Skip to contents

How to use EJAM to analyze zip codes

EJAM can analyze various kinds of Census-defined areas, including States, Counties, Tracts, Blockgroups, and Cities/CDPs/etc.

However, you may need to analyze zip codes.

Census uses ZIP Code Tabulation Areas (ZCTAs), which are generalized areal representations of United States Postal Service (USPS) ZIP Code service areas. See help("zctas", package="tigris")

Note a zip can be the same as a county fips in many cases, as with 10001!

many county fips are also a zipcode
many county fips are also a zipcode

The short way: ejamit(zipcode = )

The simplest way to analyze zip codes is to pass them directly to ejamit(), which converts them to ZCTA polygons via shapes_from_zip() and analyzes them like any other shapefile (the first use downloads a large national boundaries file, which is slow, but it is cached locally so later uses are fast):

out <- ejamit(zipcode = c("10012", "10506"))

# summary report, with the places described as zip codes and their bounds mapped
ejam2report(out)

# or just get the ZCTA polygons, to map or inspect them
z <- shapes_from_zip(c("10012", "10506"))
mapfast(z)

The rest of this article shows the manual, step-by-step version of that workflow, which offers more control (e.g., other sources of zip code polygons).

Manual step-by-step approach

The original (pre-2025) EJSCREEN API did not handle zip codes. It can show where the center of the zip code is but will not map its bounds or provide a report easily via the API.

# Just see where the zipcode is, not its boundaries
# (use zip= , not wherestr= : a bare 5-digit wherestr is now read as a county FIPS first,
#  so wherestr = '10001' would open Kent County, DE instead of this Manhattan zip code)
browseURL(url_ejscreenmap(zip = '10001'))

To map and analyze zip codes you can download shapefiles for them and analyze or map them in EJAM as shown below.

downloading zcta polygons

Takes time to download!

library(tigris)
# options(tigris_use_cache=TRUE) # done by EJAM load/attach
options(tigris_refresh=FALSE)
#zcta_DE <- zctas(starts_with = fips_from_name('DE'), keep_zipped_shapefile = T) # all in the state
#zcta1 <- zctas(starts_with = c("10001"))  # one zip
zcta2 <- zctas(starts_with = c("10012", "10506"))  # two zipcodes

z = shapefile_from_any(zcta2)

mapping zcta polygons

Another source of zip code polygons (esri service)


# Download spatial bounds for all zipcodes in Delaware
ST1 <- "DE"
url1 <- paste0("https://services.arcgis.com/P3ePLMYs2RVChkJx/ArcGIS/rest/services/USA_Boundaries_2022/FeatureServer/3/query?",
  "where=STATE%3D%27", ST1, "%27",
  "&geometryType=esriGeometryEnvelope&inSR=&spatialRel=esriSpatialRelIntersects&resultType=none&distance=0.0&units=esriSRUnit_Meter&relationParam=&returnGeodetic=false",
  "&outFields=*",
  "&returnGeometry=true",
  "&f=json")
require(httr2); require(sf); require(mapview)
response1 <- httr2::req_perform(httr2::request(url1))
text1 <- httr2::resp_body_string(response1)
shp1 <- sf::st_read(text1)
mapview::mapview(shp1)

see EJSCREEN map at a zip code

The EJScreen map app can be opened at zip code(s) via a deep link, using its ?zip= launch-URL parameter – the explicit way to pass a zip code. That matters because a bare 5-digit number is ambiguous: 10001 is both a Manhattan zip code and Kent County, DE’s county FIPS, and EJScreen deep links read a bare 5-digit wherestr= as a county FIPS first (EJAM’s convention). The zip argument of url_ejscreenmap() builds the unambiguous zip links:

# one zip code: EJScreen opens centered there, with a pin and the site report popup
browseURL(url_ejscreenmap(zip = "20019"))
#> opens https://pedp-ejscreen.azurewebsites.net/index.html?zip=20019

# note the zip parameter, unlike wherestr, is never confused with a county FIPS:
url_ejscreenmap(zip = "10001")  # zip 10001, in Manhattan, NY
url_ejscreenmap(fips = "10001") # county FIPS 10001 = Kent County, DE (boundary drawn)

# several zips in one link (the map zooms to fit all of them),
# and radius= prefills the report buffer distance in miles:
url_ejscreenmap(zip = c("20019", "20020"), combined = TRUE, radius = 1)
#> "https://pedp-ejscreen.azurewebsites.net/index.html?zip=20019,20020&radius=1"

# a zip passed as a number gets its leading zeroes restored:
url_ejscreenmap(zip = 1001) # zip 01001, Agawam, MA

These links need the EJScreen release that added deep links (see Public-Environmental-Data-Partners/EJScreen#70); the map pins the zip’s location – for analyzing the zip code’s actual boundaries, use the zcta polygons approach above.

analyzing zip codes in EJAM

You can analyze zip codes in EJAM like this:

out = ejamit(shapefile = z, radius = 0)

summary report on zipcodes

ejam2report(out, 
            analysis_title = "Zip codes",
            site_method = 'SHP', 
            shp = z)

compare sites

# put zip code in the x axis labels!
ejam2barplot_sites(out, names.arg = z$GEOID20, sortby = FALSE) # zcta2$GEOID20

# This table view works, but the links to reports do not work with zipcodes
ejam2tableviewer(out)

map of detailed results


out_plus_shape = sf::st_as_sf(
  data.frame(out$results_bysite, z)
  )
# put zip code in the map popups!
out_plus_shape$ejam_uniq_id = paste0(out_plus_shape$ejam_uniq_id, " (zip ", z$GEOID20, ")")
mapfastej(out_plus_shape)

Note that ejam2map() will not work for zipcodes!