Tennessee’s 2023 and 2026 U.S. House districts, with precinct-level results from the 2024 presidential election.


2023 Districts

2024 Presidential Election Results by Congressional District (2023)
District Dem Votes GOP Votes Total Votes Dem % GOP % GOP Margin
District 1 71,440 272,148 347,144 20.6% 78.4% 57.8%
District 2 118,987 243,979 368,028 32.3% 66.3% 34.0%
District 3 113,598 243,930 362,269 31.4% 67.3% 36.0%
District 4 93,068 240,013 337,108 27.6% 71.2% 43.6%
District 5 148,153 212,052 366,086 40.5% 57.9% 17.5%
District 6 114,886 243,100 362,445 31.7% 67.1% 35.4%
District 7 128,974 203,330 337,242 38.2% 60.3% 22.0%
District 8 101,873 246,787 352,663 28.9% 70.0% 41.1%
District 9 165,286 61,526 230,957 71.6% 26.6% -44.9%

2026 Districts

2024 Presidential Election Results by Congressional District (2026)
District Dem Votes GOP Votes Total Votes Dem % GOP % GOP Margin
District 1 71,440 272,148 347,144 20.6% 78.4% 57.8%
District 2 118,987 243,979 368,028 32.3% 66.3% 34.0%
District 3 114,338 244,184 363,309 31.5% 67.2% 35.7%
District 4 115,572 190,517 310,601 37.2% 61.3% 24.1%
District 5 119,507 188,788 312,493 38.2% 60.4% 22.2%
District 6 127,184 222,351 354,244 35.9% 62.8% 26.9%
District 7 141,570 223,806 370,711 38.2% 60.4% 22.2%
District 8 124,232 186,743 315,407 39.4% 59.2% 19.8%
District 9 123,435 194,349 322,005 38.3% 60.4% 22.0%

Code:

# ============================================================
# Step 0. INSTALL AND LOAD REQUIRED PACKAGES
# ============================================================

if (!require("tidyverse")) install.packages("tidyverse")
if (!require("sf")) install.packages("sf")
if (!require("leaflet")) install.packages("leaflet")
if (!require("leaflet.extras2")) install.packages("leaflet.extras2")
if (!require("tidycensus")) install.packages("tidycensus")
if (!require("kableExtra")) install.packages("kableExtra")
if (!require("htmlwidgets")) install.packages("htmlwidgets")

library(tidyverse)
library(sf)
library(leaflet)
library(leaflet.extras2)
library(tidycensus)
library(kableExtra)
library(htmlwidgets)

# ============================================================
# Step 1. READ PRECINCT-LEVEL 2024 RESULTS
# ============================================================

precincts <- st_read(
  "TN-precincts-with-results.geojson",
  quiet = TRUE
) %>%
  st_transform(4326) %>%
  mutate(
    pct_rep_lead = (votes_rep - votes_dem) / votes_total
  )

# ============================================================
# Step 2. SIMPLIFY PRECINCT GEOMETRY (RPUBS FIX)
# ============================================================

sf::sf_use_s2(FALSE)

precincts_simplified <- st_simplify(
  precincts,
  dTolerance = 0.0002,
  preserveTopology = TRUE
)

sf::sf_use_s2(TRUE)

# ============================================================
# Step 3. LOAD & STANDARDIZE DISTRICT GEOMETRY
# ============================================================

# --- 2023 Enacted Districts ---
cd_2023 <- get_acs(
  geography = "congressional district",
  state = "TN",
  variables = "B01001_001",
  year = 2023,
  survey = "acs5",
  geometry = TRUE
) %>%
  st_transform(4326) %>%
  mutate(
    district_num = stringr::str_extract(NAME, "\\d+"),
    cd_name = paste0("District ", district_num),
    label = district_num
  ) %>%
  select(cd_name, label, geometry)

# --- 2026 Proposed Districts ---
NewDistricts <- st_read(
  "NewCongressional26.shp",
  quiet = TRUE
) %>%
  st_transform(4326) %>%
  st_make_valid() %>%
  mutate(
    cd_name = paste0("District ", DISTRICT),
    label = as.character(DISTRICT)
  ) %>%
  select(cd_name, label, DISTRICT, geometry)

# ============================================================
# Step 4. COLOR PALETTE (REPUBLICAN MARGIN)
# ============================================================

pal <- colorNumeric(
  palette  = "RdBu",
  domain   = precincts_simplified$pct_rep_lead,
  reverse  = TRUE,
  na.color = "transparent"
)

popup_text <- ~paste0(
  "<b>Precinct GEOID:</b> ", GEOID, "<br><br>",
  "<b>Dem votes:</b> ", scales::comma(votes_dem), "<br>",
  "<b>GOP votes:</b> ", scales::comma(votes_rep), "<br>",
  "<b>Total votes:</b> ", scales::comma(votes_total), "<br><br>",
  "<b>Republican margin:</b> ",
  round(100 * pct_rep_lead, 1), " pts"
)

# ============================================================
# Step 5. TRACT → DISTRICT MATCHING (OPTION A)
# ============================================================

sf::sf_use_s2(FALSE)

precinct_centroids <- precincts_simplified %>%
  st_point_on_surface()

summarise_district_votes <- function(points, districts) {
  points %>%
    st_join(districts, join = st_within) %>%
    st_drop_geometry() %>%
    group_by(cd_name) %>%
    summarise(
      Dem_Votes   = sum(votes_dem, na.rm = TRUE),
      GOP_Votes   = sum(votes_rep, na.rm = TRUE),
      Total_Votes = sum(votes_total, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      `Dem %`      = sprintf("%.1f%%", 100 * Dem_Votes / Total_Votes),
      `GOP %`      = sprintf("%.1f%%", 100 * GOP_Votes / Total_Votes),
      `GOP Margin` = sprintf("%.1f%%",
                             100 * (GOP_Votes - Dem_Votes) / Total_Votes),
      `Dem Votes`   = scales::comma(Dem_Votes),
      `GOP Votes`   = scales::comma(GOP_Votes),
      `Total Votes` = scales::comma(Total_Votes)
    ) %>%
    select(
      District = cd_name,
      `Dem Votes`,
      `GOP Votes`,
      `Total Votes`,
      `Dem %`,
      `GOP %`,
      `GOP Margin`
    )
}

district_2023 <- summarise_district_votes(precinct_centroids, cd_2023)
district_2026 <- summarise_district_votes(precinct_centroids, NewDistricts)

sf::sf_use_s2(TRUE)

# ============================================================
# Step 6. DISTRICT LABEL POINTS
# ============================================================

cd_2023_labels <- cd_2023 %>% st_point_on_surface()
cd_2026_labels <- NewDistricts %>% st_point_on_surface()

# ============================================================
# Step 7. MAP 1: PRECINCT RESULTS + 2023 DISTRICTS
# ============================================================

Map_2023 <- leaflet(precincts_simplified) %>%
  addProviderTiles("CartoDB.Positron", group = "Positron (Light)") %>%
  addProviderTiles("Esri.WorldStreetMap", group = "Street Map") %>%
  addProviderTiles("Esri.WorldImagery", group = "Satellite") %>%
  
  addPolygons(
    fillColor   = ~pal(pct_rep_lead),
    fillOpacity = 0.75,
    color       = "#555555",
    weight      = 0.25,
    popup       = popup_text,
    group       = "2024 Precinct Results"
  ) %>%
  
  addPolylines(
    data  = cd_2023,
    color = "black",
    weight = 2,
    group = "Congressional Districts"
  ) %>%
  
  addLabelOnlyMarkers(
    data  = cd_2023_labels,
    label = ~label,
    labelOptions = labelOptions(
      noHide = TRUE,
      direction = "center",
      textsize = "12px",
      style = list("font-weight" = "bold")
    ),
    group = "District Labels"
  ) %>%
  
  addLegend(
    pal = pal,
    values = ~pct_rep_lead,
    title = "Republican margin<br>(percentage points)",
    position = "topright",
    labFormat = labelFormat(
      transform = function(x) x * 100,
      suffix = "%"
    )
  ) %>%
  
  addLayersControl(
    baseGroups = c("Positron (Light)", "Street Map", "Satellite"),
    overlayGroups = c(
      "2024 Precinct Results",
      "Congressional Districts",
      "District Labels"
    ),
    options = layersControlOptions(position = "bottomleft", collapsed = TRUE)
  )

Map_2023

# ============================================================
# Step 8. TABLE 1: DISTRICT RESULTS (2023)
# ============================================================

Table_2023 <- district_2023 %>%
  kbl(
    format  = "html",
    caption = "2024 Presidential Election Results by Congressional District (2023)"
  ) %>%
  kable_styling(
    full_width = FALSE,
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(0, bold = TRUE)

Table_2023

# ============================================================
# Step 9. MAP 2: PRECINCT RESULTS + 2026 DISTRICTS
# ============================================================

Map_2026 <- leaflet(precincts_simplified) %>%
  addProviderTiles("CartoDB.Positron", group = "Positron (Light)") %>%
  addProviderTiles("Esri.WorldStreetMap", group = "Street Map") %>%
  addProviderTiles("Esri.WorldImagery", group = "Satellite") %>%
  
  addPolygons(
    fillColor   = ~pal(pct_rep_lead),
    fillOpacity = 0.75,
    color       = "#555555",
    weight      = 0.25,
    popup       = popup_text,
    group       = "2024 Precinct Results"
  ) %>%
  
  addPolylines(
    data  = NewDistricts,
    color = "black",
    weight = 2,
    group = "Congressional Districts"
  ) %>%
  
  addLabelOnlyMarkers(
    data  = cd_2026_labels,
    label = ~label,
    labelOptions = labelOptions(
      noHide = TRUE,
      direction = "center",
      textsize = "12px",
      style = list("font-weight" = "bold")
    ),
    group = "District Labels"
  ) %>%
  
  addLegend(
    pal = pal,
    values = ~pct_rep_lead,
    title = "Republican margin<br>(percentage points)",
    position = "topright",
    labFormat = labelFormat(
      transform = function(x) x * 100,
      suffix = "%"
    )
  ) %>%
  
  addLayersControl(
    baseGroups = c("Positron (Light)", "Street Map", "Satellite"),
    overlayGroups = c(
      "2024 Precinct Results",
      "Congressional Districts",
      "District Labels"
    ),
    options = layersControlOptions(position = "bottomleft", collapsed = TRUE)
  )

Map_2026

# ============================================================
# Step 10. TABLE 2: DISTRICT RESULTS (2026)
# ============================================================

Table_2026 <- district_2026 %>%
  kbl(
    format  = "html",
    caption = "2024 Presidential Election Results by Congressional District (2026)"
  ) %>%
  kable_styling(
    full_width = FALSE,
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  row_spec(0, bold = TRUE)

Table_2026

# ============================================================
# Step 11. EXPORT MAPS AND TABLES
# ============================================================

saveWidget(Map_2023, "PrecinctResults_2023_Map.html", selfcontained = TRUE)
saveWidget(Map_2026, "PrecinctResults_2026_Map.html", selfcontained = TRUE)

save_kable(Table_2023, "PrecinctResults_2023_Table.html")
save_kable(Table_2026, "PrecinctResults_2026_Table.html")