Zillow.com is a large online real estate platform that provides information and tools related to buying, selling, renting, and financing homes. The platform’s offerings include property listings, home value estimates, rental listings, and housing market data for consumers, researchers, and professionals.
Zillow’s publicly available market metrics include the Zillow Observed Rent Index (ZORI), which measures typical market rents based on observed rental listings. The index is designed to track rent changes over time rather than asking rents for specific units. At the ZIP‑code level, ZORI values show the estimated typical rent for a given area, reflecting local rental market conditions and how rents are rising or falling within relatively small geographic markets. The data are useful for comparing neighborhood-level rent trends and affordability across cities and regions.
Below is a map showing the four ZIP codes closest to MTSU’s campus and, below the map, a table of monthly ZORI estimates for each ZIP code between Jan. 1, 2024 and Feb. 1, 2026.
MTSU-area ZIP codes and ZORI estimates
| Zillow Observed Rent Index (ZORI) | ||||
| Monthly by ZIP — Murfreesboro, TN | ||||
| Month | 37127 | 37128 | 37129 | 37130 |
|---|---|---|---|---|
| 2024-01-01 | 1635 | 1643 | 1768 | 1442 |
| 2024-02-01 | 1641 | 1651 | 1744 | 1437 |
| 2024-03-01 | 1659 | 1684 | 1750 | 1435 |
| 2024-04-01 | 1695 | 1707 | 1765 | 1430 |
| 2024-05-01 | 1699 | 1721 | 1789 | 1448 |
| 2024-06-01 | 1688 | 1708 | 1804 | 1459 |
| 2024-07-01 | 1697 | 1709 | 1816 | 1486 |
| 2024-08-01 | 1688 | 1711 | 1837 | 1493 |
| 2024-09-01 | 1689 | 1717 | 1828 | 1481 |
| 2024-10-01 | 1693 | 1707 | 1833 | 1469 |
| 2024-11-01 | 1701 | 1719 | 1801 | 1461 |
| 2024-12-01 | 1690 | 1715 | 1809 | 1448 |
| 2025-01-01 | 1663 | 1715 | 1795 | 1444 |
| 2025-02-01 | 1666 | 1694 | 1793 | 1462 |
| 2025-03-01 | 1656 | 1696 | 1788 | 1495 |
| 2025-04-01 | 1650 | 1696 | 1813 | 1507 |
| 2025-05-01 | 1670 | 1700 | 1834 | 1497 |
| 2025-06-01 | 1685 | 1708 | 1846 | 1500 |
| 2025-07-01 | 1724 | 1711 | 1827 | 1520 |
| 2025-08-01 | 1705 | 1733 | 1826 | 1521 |
| 2025-09-01 | 1681 | 1730 | 1824 | 1538 |
| 2025-10-01 | 1656 | 1735 | 1829 | 1530 |
| 2025-11-01 | 1671 | 1712 | 1807 | 1525 |
| 2025-12-01 | 1685 | 1710 | 1804 | 1513 |
| 2026-01-01 | 1704 | 1723 | 1809 | 1508 |
| 2026-02-01 | 1699 | 1722 | 1823 | 1512 |
Here is the R code that produced the map and table:
# =============================================================================
# ZORI by ZIP for Murfreesboro, TN — streamlined
# Download → Clean → Reshape → Visualize (plotly) → Tabulate (gt) → Map (leaflet)
# =============================================================================
# ---- Parameters --------------------------------------------------------------
CITY <- "Murfreesboro"
STATE <- "TN"
MURF_ZIPS <- c("37127", "37128", "37129", "37130")
MONTH_WINDOW <- 25 # change to 36 if desired
ZILLOW_URL <- "https://files.zillowstatic.com/research/public_csvs/zori/Zip_zori_uc_sfrcondomfr_sm_month.csv?t=1773853995"
# ---- Libraries ---------------------------------------------------------------
suppressPackageStartupMessages({
library(readr)
library(dplyr)
library(janitor)
library(lubridate)
library(tidyr)
library(plotly)
library(scales)
library(gt)
library(sf)
library(leaflet)
library(RColorBrewer)
library(htmltools)
})
# =============================================================================
# A) Acquire & prepare Zillow ZORI
# =============================================================================
local_file <- tempfile(fileext = ".csv")
download.file(ZILLOW_URL, local_file, mode = "wb")
zori <- read_csv(local_file, show_col_types = FALSE) |>
clean_names()
# Keep only city/state first (faster pivot)
zori_murf <- zori |>
filter(state == STATE, city == CITY)
# ---- Pivot then parse date names in mutate() -------------------------
zori_murf_long <- zori_murf |>
pivot_longer(
cols = matches("^\\d{1,2}/\\d{1,2}/\\d{4}$|^x?\\d{4}_\\d{2}_\\d{2}$"),
names_to = "date",
values_to = "zori"
) |>
mutate(
date = sub("^x", "", date),
date = ifelse(
grepl("/", date),
as.character(lubridate::mdy(date)), # m/d/yyyy
as.character(lubridate::ymd(gsub("_", "-", date))) # yyyy_mm_dd -> yyyy-mm-dd
),
date = as.Date(date)
) |>
arrange(region_name, date)
# =============================================================================
# B) Filter window & visualize
# =============================================================================
most_recent_date <- max(zori_murf_long$date, na.rm = TRUE)
cutoff_date <- most_recent_date %m-% months(MONTH_WINDOW)
zori_murf_window <- zori_murf_long |>
filter(date >= cutoff_date, !is.na(zori))
# ---- Plotly line chart -------------------------------------------------------
ZORIplot <- plot_ly(
data = zori_murf_window,
x = ~date, y = ~zori,
color = ~region_name,
type = "scatter", mode = "lines",
hoverinfo = "text",
text = ~paste(
"ZIP:", region_name,
"<br>Date:", format(date, "%Y-%m-%d"),
"<br>ZORI:", dollar(zori)
)
) |>
layout(
title = paste0("ZORI Rent Trends by ZIP — ", CITY, ", ", STATE),
xaxis = list(title = "Date"),
yaxis = list(title = "Typical Rent (ZORI estimate, $)"),
legend = list(title = list(text = "<b>ZIP Code</b>"))
)
ZORIplot
# =============================================================================
# C) Tables (long → month-level wide → gt)
# =============================================================================
zori_wide <- zori_murf_window |>
transmute(
Month = floor_date(date, "month"),
ZIP = region_name,
ZORI = round(zori, 0)
) |>
group_by(Month, ZIP) |>
summarize(ZORI = mean(ZORI, na.rm = TRUE), .groups = "drop") |>
arrange(Month, ZIP) |>
pivot_wider(names_from = ZIP, values_from = ZORI) |>
arrange(Month)
ZORISummary <- zori_wide |>
gt() |>
tab_header(
title = "Zillow Observed Rent Index (ZORI)",
subtitle = paste0("Monthly by ZIP — ", CITY, ", ", STATE)
)
ZORISummary
# =============================================================================
# D) ZIP code map (ZCTAs)
# =============================================================================
# Download once per session (TIGER/Line ZCTA 2020)
zipfile <- "ZCTAs2020.zip"
if (!file.exists(zipfile)) {
download.file(
"https://www2.census.gov/geo/tiger/GENZ2020/shp/cb_2020_us_zcta520_500k.zip",
zipfile, mode = "wb"
)
}
unzip(zipfile, exdir = "ZCTAs2020", overwrite = FALSE)
ZCTAMap <- suppressWarnings(read_sf(file.path("ZCTAs2020", "cb_2020_us_zcta520_500k.shp"))) |>
filter(GEOID20 %in% MURF_ZIPS) |>
st_make_valid() |>
st_transform(4326)
zcta_ids <- as.character(ZCTAMap$GEOID20)
n <- length(zcta_ids)
pal_vec <- if (n <= 1) "#66C2A5" else if (n <= 8) brewer.pal(max(3, n), "Set2")[seq_len(n)] else colorRampPalette(brewer.pal(8, "Set2"))(n)
pal <- colorFactor(pal_vec, domain = zcta_ids)
bb <- st_bbox(ZCTAMap)
lng1 <- as.numeric(bb["xmin"]); lat1 <- as.numeric(bb["ymin"])
lng2 <- as.numeric(bb["xmax"]); lat2 <- as.numeric(bb["ymax"])
FinalMap <- leaflet(options = leafletOptions(minZoom = 6)) |>
addProviderTiles(providers$Esri.WorldStreetMap, group = "Esri World Street Map") |>
addProviderTiles(providers$CartoDB.Positron, group = "Light (Positron)") |>
addPolygons(
data = ZCTAMap,
fillColor = ~pal(GEOID20),
fillOpacity = 0.45,
color = "#444444",
weight = 1.2,
opacity = 0.9,
label = lapply(sprintf("<strong>ZCTA:</strong> %s", htmlEscape(zcta_ids)), HTML),
popup = ~paste0("<b>ZCTA:</b> ", GEOID20),
highlightOptions = highlightOptions(weight = 3, color = "#000", fillOpacity = 0.55, bringToFront = TRUE)
) |>
fitBounds(lng1, lat1, lng2, lat2) |>
addLegend("bottomright", pal = pal, values = zcta_ids, title = paste0(CITY, " ZIP codes"), opacity = 0.7) |>
addLayersControl(baseGroups = c("Esri World Street Map", "Light (Positron)"),
options = layersControlOptions(collapsed = TRUE))
FinalMap