Here’s a quick map of the U.S. Department of Housing and Urban Development’s current two-bedroom Fair Market Rent estimates for the four Rutherford County ZIP codes closest to MTSU’s campus. Below the map, tables show estimates for all rental unit sizes at present, five years ago, and the change since five years ago.


Two-bedroom Fair Market Rent in MTSU-area ZIP Codes
Current Fair Market Rents
ZIP Studio_Current OneBR_Current TwoBR_Current ThreeBR_Current FourBR_Current
37127 $1,360 $1,420 $1,560 $1,990 $2,430
37128 $1,570 $1,640 $1,800 $2,300 $2,800
37129 $1,570 $1,640 $1,800 $2,300 $2,800
37130 $1,280 $1,340 $1,470 $1,880 $2,290
37132 $1,280 $1,340 $1,470 $1,880 $2,290
Previous Fair Market Rents
ZIP Studio_Previous OneBR_Previous TwoBR_Previous ThreeBR_Previous FourBR_Previous
37127 $1,080 $1,110 $1,250 $1,560 $1,930
37128 $1,330 $1,360 $1,540 $1,930 $2,380
37129 $1,060 $1,080 $1,220 $1,530 $1,890
37130 $1,060 $1,080 $1,220 $1,530 $1,890
37132 $1,170 $1,200 $1,350 $1,690 $2,090
Change in Fair Market Rents
ZIP Studio_Change OneBR_Change TwoBR_Change ThreeBR_Change FourBR_Change
37127 $280 $310 $310 $430 $500
37128 $240 $280 $260 $370 $420
37129 $510 $560 $580 $770 $910
37130 $220 $260 $250 $350 $400
37132 $110 $140 $120 $190 $200

Code:

# ==========================================================
# 1. Install and load required packages
# ==========================================================
if (!require("tidyverse")) install.packages("tidyverse")
if (!require("httr")) install.packages("httr")
if (!require("openxlsx")) install.packages("openxlsx")
if (!require("kableExtra")) install.packages("kableExtra")
if (!require("tidycensus")) install.packages("tidycensus")
if (!require("sf")) install.packages("sf")
if (!require("leaflet")) install.packages("leaflet")

library(tidyverse)
library(httr)
library(openxlsx)
library(kableExtra)
library(tidycensus)
library(sf)
library(leaflet)

options(tigris_use_cache = TRUE)

# ==========================================================
# 2. Define URLs (manually editable)
# ==========================================================
url_current  <- "https://www.huduser.gov/portal/datasets/fmr/fmr2026/fy2026_safmrs_revised.xlsx"
url_previous <- "https://www.huduser.gov/portal/datasets/fmr/fmr2023/fy2023_safmrs_revised.xlsx"

file_current  <- "Current_data.xlsx"
file_previous <- "Previous_data.xlsx"

# ==========================================================
# 3. Download CURRENT dataset
# ==========================================================
res_current <- GET(
  url_current,
  write_disk(file_current, overwrite = TRUE),
  add_headers(
    "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
    "Accept" = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "Referer" = "https://www.huduser.gov/",
    "Connection" = "keep-alive"
  )
)

print(status_code(res_current))

if (!file.exists(file_current) || file.info(file_current)$size == 0) {
  stop("Current file download failed or is empty")
}

# ==========================================================
# 4. Download PREVIOUS dataset
# ==========================================================
res_previous <- GET(
  url_previous,
  write_disk(file_previous, overwrite = TRUE),
  add_headers(
    "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
    "Accept" = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "Referer" = "https://www.huduser.gov/",
    "Connection" = "keep-alive"
  )
)

print(status_code(res_previous))

if (!file.exists(file_previous) || file.info(file_previous)$size == 0) {
  stop("Previous file download failed or is empty")
}

# ==========================================================
# 5. Read Excel files
# ==========================================================
current_data  <- read.xlsx(file_current, sheet = 1)
previous_data <- read.xlsx(file_previous, sheet = 1)

# ==========================================================
# 6. Clean and standardize variables
# ==========================================================
current_clean <- current_data %>%
  rename_with(~ str_replace_all(.x, " ", "_")) %>%
  mutate(ZIP.Code = as.character(ZIP.Code)) %>%
  select(
    ZIP = ZIP.Code,
    Studio = SAFMR.0BR,
    OneBR = SAFMR.1BR,
    TwoBR = SAFMR.2BR,
    ThreeBR = SAFMR.3BR,
    FourBR = SAFMR.4BR
  ) %>%
  distinct(ZIP, .keep_all = TRUE) %>%
  rename_with(~ paste0(.x, "_Current"), -ZIP)

previous_clean <- previous_data %>%
  rename_with(~ str_replace_all(.x, " ", "_")) %>%
  mutate(ZIP.Code = as.character(ZIP.Code)) %>%
  select(
    ZIP = ZIP.Code,
    Studio = SAFMR.0BR,
    OneBR = SAFMR.1BR,
    TwoBR = SAFMR.2BR,
    ThreeBR = SAFMR.3BR,
    FourBR = SAFMR.4BR
  ) %>%
  distinct(ZIP, .keep_all = TRUE) %>%
  rename_with(~ paste0(.x, "_Previous"), -ZIP)

# ==========================================================
# 7. Merge datasets
# ==========================================================
merged_data <- current_clean %>%
  left_join(previous_clean, by = "ZIP")

# ==========================================================
# 8. Calculate changes
# ==========================================================
merged_data <- merged_data %>%
  mutate(
    Studio_Change  = Studio_Current  - Studio_Previous,
    OneBR_Change   = OneBR_Current   - OneBR_Previous,
    TwoBR_Change   = TwoBR_Current   - TwoBR_Previous,
    ThreeBR_Change = ThreeBR_Current - ThreeBR_Previous,
    FourBR_Change  = FourBR_Current  - FourBR_Previous
  )

# ==========================================================
# 9. Filter data
# ==========================================================
ZIPList <- c("37127","37128","37129","37130","37132")

filtered_data <- merged_data %>%
  filter(ZIP %in% ZIPList)

# ==========================================================
# 10. Tables (kableExtra)
# ==========================================================

# Current
filtered_data %>%
  select(ZIP, ends_with("_Current")) %>%
  mutate(across(-ZIP, scales::dollar)) %>%
  kable(caption = "Current Fair Market Rents") %>%
  kable_styling(full_width = FALSE)

# Previous
filtered_data %>%
  select(ZIP, ends_with("_Previous")) %>%
  mutate(across(-ZIP, scales::dollar)) %>%
  kable(caption = "Previous Fair Market Rents") %>%
  kable_styling(full_width = FALSE)

# Change
filtered_data %>%
  select(ZIP, ends_with("_Change")) %>%
  mutate(across(-ZIP, scales::dollar)) %>%
  kable(caption = "Change in Fair Market Rents") %>%
  kable_styling(full_width = FALSE)

# ==========================================================
# 11. Get ZIP geometry
# ==========================================================
zcta_map <- get_acs(
  geography = "zcta",
  variables = "B01001_001",
  year = 2020,
  geometry = TRUE
)

# ==========================================================
# 12. Join data to map
# ==========================================================
map_data <- zcta_map %>%
  rename(ZIP = GEOID) %>%
  left_join(merged_data, by = "ZIP") %>%
  filter(ZIP %in% ZIPList)

# ==========================================================
# 13. Create improved popup labels
# ==========================================================
map_data <- map_data %>%
  mutate(
    popup = paste0(
      "<strong>ZIP: ", ZIP, "</strong><br><br>",
      
      "<strong>Current (2BR):</strong> ", scales::dollar(TwoBR_Current), "<br>",
      "<strong>Previous (2BR):</strong> ", scales::dollar(TwoBR_Previous), "<br>",
      "<strong>Change:</strong> ", scales::dollar(TwoBR_Change)
    )
  )

# ==========================================================
# 14. Fix CRS (important)
# ==========================================================
map_data <- st_transform(map_data, 4326)

# ==========================================================
# 15. Improved color palette
# ==========================================================
pal_current <- colorNumeric(
  palette = "viridis",
  domain = map_data$TwoBR_Current,
  na.color = "transparent"
)

# ==========================================================
# 16. Leaflet map (Current only, improved styling)
# ==========================================================
leaflet(map_data) %>%
  addProviderTiles("CartoDB.Voyager") %>%
  
  addPolygons(
    fillColor = ~pal_current(TwoBR_Current),
    color = "black",          # ✅ thin black borders
    weight = 0.7,
    fillOpacity = 0.6,        # ✅ slightly stronger fill
    popup = ~popup
  ) %>%
  
  addLegend(
    pal = pal_current,
    values = ~TwoBR_Current,
    title = "Current (2BR FMR)",
    position = "topright"
  )