Overview

Spotting a good deal on a rental home can be difficult without knowing what rental homes in the area typically cost. But you can reduce the guesswork by mapping the U.S. Department of Housing and Urban Development’s latest small-area fair market rent estimates for local ZIP codes. Here’s a map shaded by each ZIP code’s estimated two-bedroom rent. Click or tap a ZIP code to see estimates for other rental home sizes in the ZIP code, plus the ZIP code’s Census-estimated number of occupied rental homes.

MTSU’s campus is the small, roughly rectangular shape near the center of the map, in the lower-left portion of ZIP code 37130. MTSU has its own ZIP code, 37132, but its rent estimates simply match those of the larger 37130 ZIP code.

A few perhaps useful observations:

Produced each year for selected ZIP codes in more densely populated areas of the country, small-area fair market rent estimates for an area reflect the monthly rent figure - with utilities included - below which the rent for 40 percent of standard quality rental housing units fall within the area.

HUD’s method for producing the estimates draws from the distribution of rents of all rental units in the area that are occupied by recent movers, excluding public housing units, newly built units, and substandard units. See the relevant part of the Code of Federal Regulations for details.

HUD uses these estimates to figure out how much money to provide in federal housing assistance. But you can use the same figures to get an idea of what a reasonable rent might be for a standard-quality rental unit in a given ZIP code. HUD provides estimates for different sizes of rental units, from a studio to a four-bedroom unit.

Making the map with R

Students who take my Data Skills for Media Professionals course learn to produce this and other analyses of publicly available datasets using the R programming language. Here’s a step-by-step explanation of how R made the map.

Required packages

To retrieve and map the data, R will need to install and load the following packages:

if (!require("dplyr")) install.packages("dplyr")
if (!require("tidyverse")) install.packages("tidyverse")
if (!require("mapview")) install.packages("mapview")
if (!require("leaflet")) install.packages("leaflet")
if (!require("leaflet.extras2")) install.packages("leaflet.extras2")
if (!require("tidycensus")) install.packages("tidycensus")
if (!require("sf")) install.packages("sf")
if (!require("openxlsx")) install.packages("openxlsx")
if (!require("scales")) install.packages("scales")
if (!require("leaflet.extras2")) install.packages("leaflet.extras2")

library(dplyr)
library(tidyverse)
library(ggplot2) #From the tidyverse package
library(readr) #From the tidyverse package
library(leaflet)
library(tidycensus)
library(sf)
library(mapview)
library(openxlsx)
library(leaflet.extras2)
library(leafpop)
library(scales)

options(tigris_use_cache = TRUE)
options(scipen = 999)

Specifying ZIP codes

The script will need a list of the ZIP codes that you want to examine. This example uses a list of ZIP codes in, or mostly in, Rutherford County, Tennessee. Any valid list of any number of ZIP codes can be used. If you edit the list, just be sure to use quotes, commas and brackets in exactly the same way the example does. The lookup tool for a given year on HUD’s Small Area Fair Market Rents page can give you a list of usable ZIP codes for a given area. Note that ZIP codes can straddle county borders, and HUDs ZIP code list may not include all ZIP codes in a given area.

ZIPList <- c(
  "37127",
  "37128",
  "37129",
  "37130",
  "37132",
  "37085",
  "37118",
  "37149",
  "37037",
  "37153",
  "37167",
  "37086"
)

Retrieving and cleaning the rent data

This portion of the script will retrieve HUD’s small-area fair market rent estimates for a given year, filter the data for the specified ZIP codes, then do some data paring, labeling, formatting, and data cleaning.

This example retrieves HUD’s 2025 dataset, which is available in Excel format from the URL specified in the code: https://www.huduser.gov/portal/datasets/fmr/fmr2025/fy2025_safmrs.xlsx. To get the URL for a different year’s data, visit HUD’s Small Area Fair Market Rents page, then copy and paste the URL for the year you want.

FMR = read.xlsx(
  "https://www.huduser.gov/portal/datasets/fmr/fmr2025/fy2025_safmrs.xlsx",
  sheet = 1
)
FMR <- FMR[FMR$ZIP.Code %in% ZIPList, ]
keepvars <- c("ZIP.Code",
              "SAFMR.0BR",
              "SAFMR.1BR",
              "SAFMR.2BR",
              "SAFMR.3BR",
              "SAFMR.4BR")
FMR <- FMR[keepvars]
colnames(FMR) <- c("ZIP", "Studio", "BR1", "BR2", "BR3", "BR4")
FMR$Zero_BR <- FMR$Studio
FMR$One_BR <- FMR$BR1
FMR$Two_BR <- FMR$BR2
FMR$Three_BR <- FMR$BR3
FMR$Four_BR <- FMR$BR4
FMR$Studio <- dollar(FMR$Studio)
FMR$BR1 <- dollar(FMR$BR1)
FMR$BR2 <- dollar(FMR$BR2)
FMR$BR3 <- dollar(FMR$BR3)
FMR$BR4 <- dollar(FMR$BR4)

# Deleting duplicate rows

FMR <- distinct(FMR)

Getting a ZIP code map

ZIP codes can change over time, so the script uses the latest-available ZIP code map from the U.S. Census Bureau. Accessing the Census Bureau’s API requires an API authorization key, which you can get for free, and almost instantaneously, by completing and submitting the Census Bureau’s online Key Request Form. The key will arrive by e-mail and will be a long string of characters and numbers. The Census Burea expects you to keep your key private, the way you would a password. That’s why I’m not showing you my key here.

Once you have your key, paste it in place of PasteYourAPIKeyBetwenTheseQuoteMarks in the code below.

census_api_key("PasteYourAPIKeyBetwenTheseQuoteMarks")

Now, run this code to get the map. Initially, the map will contain every ZIP Code in the U.S. and its territories. The code will filter it for the ZIP codes you specified above. The code will toss in each ZIP code’s population estimate, because this particular Census API has to toss something in. It won’t give you a map and nothing else.

At present, the latest-available ZIP Code map is from 2021. If a more recent year is available, change 2021 in year = 2021 to the more recent year. If you’re not sure whether a more recent year is available, simply give it a try. The script will return an error if the newer year is still unavailable.

Note: The map will show Census ZIP Code Tabulation Areas, or ZCTAs, which resemble ZIP codes but may not exactly correspond to ZIP Codes as defined by the U.S. Postal Service. In short, the map will be close enough, but not exactly accurate.

ZCTAMap <- get_acs(
  geography = "zcta",
  variables = c(
    Population = "B01001_001",
    Rentals_ = "DP04_0047",
    Rentals_P = "DP04_0047P"
  ),
  year = 2021,
  survey = "acs5",
  output = "wide",
  geometry = TRUE
)

# Filtering and formatting the map data

RCMap <- ZCTAMap[ZCTAMap$GEOID %in% ZIPList, ]

RCMap <- rename(RCMap,
                ZIP = GEOID,
                Rentals = Rentals_E,
                Pct = Rentals_PE)

Producing the map

All R has to do now is merge the rent data from HUD with the map from the Census Bureau, then display the map.

By default, R will shade each ZIP code by its two-bedroom fair market rent estimate. If you want to shade the ZIP codes by the fair market rent for some other size of rental unit, change zcol = "Two_BR" to zcol = "Zero_BR", zcol = "One_BR", zcol = "Three_BR", or zcol = "Four_BR".

Once R displays the map, you can click the “stack of cards” icon in the top-left corner of the map, under the “+” and “-” zoom controls, to choose different base maps, including an OpenStreetMap, which gives a lot of details about the names of streets and roads, and an ESIR.WorldImagery map, which shows satellite imagery. The map is zoomable and moveable. Each area’s ZIP code will show as you hover over the area. Click on an area, and a table will pop up showing the area’s rent estimates for various sizes of units.

RCMap_plus <- RCMap %>%
  left_join(FMR, by = c("ZIP" = "ZIP"))

mapviewOptions(basemaps.color.shuffle = FALSE)

ZIPMap <- mapview(
  RCMap_plus,
  zcol = "Two_BR",
  col.regions = RColorBrewer::brewer.pal(9, "Blues"),
  alpha.regions = .5,
  layer.name = "Fair Market Rent",
  popup = popupTable(
    RCMap_plus,
    feature.id = FALSE,
    row.numbers = FALSE,
    zcol = c("ZIP", "Studio", "BR1", "BR2", "BR3", "BR4", "Rentals", "Pct")
  )
)

ZIPMap

Saving the data in .kml and .csv formats

Not everyone will need or want it, but some users might appreciate having the map’s data in .kml or .csv format, or both. The .kml-formatted data can be imported into a Google My Maps layer. The .csv-formatted data can be imported into Microsoft Excel, Google Sheets, or just about any other spreadsheet or data analysis application.

The script will save the files on your computer’s hard drive, in the same subdirectory where the script is stored. The .kml file will be called FMRMap.kml, and the .csv file will be called FMRData.csv. Each time you run the script, the latest versions of both files will overwrite any existing versions.

# Making a .kml Map File
st_write(RCMap_plus,"FMRMap.kml", append = FALSE)
## Writing layer `FMRMap' to data source `FMRMap.kml' using driver `KML'
## Writing 12 features with 18 fields and geometry type Multi Polygon.
# Making a CSV data file
RCMap_plus_data <- st_drop_geometry(RCMap_plus, drop = TRUE)
write_csv(RCMap_plus_data,"FMRData.csv")