Redistricting and Minority Voter Representation in Tennessee

The maps and graphics suggest that Tennessee’s 2022 congressional redistricting had important consequences for minority voters. The 2021 and 2022 congressional district maps show the estimated percentage of residents who are white in each Tennessee U.S. House district, which helps identify where minority populations are more concentrated. The error bar charts compare those estimates across districts while also showing the margin of error.

The Davidson County scatterplot shows a strong relationship between precinct racial composition and support for Harris in the 2024 presidential election. In general, precincts with higher percentages of nonwhite residents also had higher percentages of votes cast for Harris. This matters because if minority communities are split across districts, their ability to influence election outcomes may be weakened.

Based on the maps and graphics, there is evidence to question whether the 2022 redistricting treated minority voters fairly. The data does not prove intent by itself, but it does show that race and voting behavior are closely connected. Because of that, the way district lines were drawn likely had a meaningful effect on minority voter representation.


U.S. House Districts in Tennessee, 2021


U.S. House Districts in Tennessee, 2022


Davidson County Precincts, 2024

Code:

library(tidyverse)
library(tidycensus)
library(sf)
library(leaflet)
library(plotly)

census_api_key("04d318cbd6d7417e56fec7f3873d8302c28fe21d", install = FALSE)

VariableList <- c(WhitePct = "DP05_0037P")

district_order <- c(1, 6, 2, 3, 7, 4, 8, 5, 9)

# --------------------
# 2021 DATA
# --------------------
mydata_2021 <- get_acs(
  geography = "congressional district",
  state = "TN",
  variables = VariableList,
  year = 2021,
  survey = "acs1",
  output = "wide",
  geometry = TRUE
)

mapdata_2021 <- mydata_2021 %>%
  rename(Area = NAME) %>%
  mutate(
    DistrictNumber = as.numeric(str_extract(Area, "\\d+")),
    Estimate = WhitePctE,
    Range = WhitePctM
  ) %>%
  st_as_sf() %>%
  st_transform(4326)

qs_2021 <- quantile(
  mapdata_2021$Estimate,
  probs = seq(0, 1, length.out = 6),
  na.rm = TRUE
)

pal_2021 <- colorBin(
  palette = "Blues",
  domain = mapdata_2021$Estimate,
  bins = qs_2021,
  pretty = FALSE
)

plotdf_2021 <- mapdata_2021 %>%
  st_drop_geometry() %>%
  mutate(
    point_color = pal_2021(Estimate),
    y_ordered = factor(DistrictNumber, levels = rev(district_order)),
    hover_text = paste0(
      "Area: ", Area,
      "<br>Estimate: ", round(Estimate, 1),
      "<br>Plus/Minus: ", round(Range, 1)
    )
  )

mygraph_2021 <- plot_ly(
  data = plotdf_2021,
  x = ~Estimate,
  y = ~y_ordered,
  type = "scatter",
  mode = "markers",
  showlegend = FALSE,
  marker = list(
    color = ~point_color,
    size = 8
  ),
  error_x = list(
    type = "data",
    array = ~Range,
    arrayminus = ~Range,
    visible = TRUE
  ),
  text = ~hover_text,
  hoverinfo = "text"
) %>%
  layout(
    title = list(
      text = "Estimates by area<br><sup>County subdivisions. Brackets show error margins.</sup>"
    ),
    xaxis = list(title = "ACS estimate"),
    yaxis = list(title = "")
  )

mapdata_2021$popup <- paste0(
  "<strong>", mapdata_2021$Area, "</strong><br/>",
  "<hr>",
  "Estimate: ", round(mapdata_2021$Estimate, 1), "<br/>",
  "Plus/Minus: ", round(mapdata_2021$Range, 1)
)

DivisionMap_2021 <- leaflet(mapdata_2021) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addPolygons(
    fillColor = ~pal_2021(Estimate),
    fillOpacity = 0.6,
    color = "black",
    weight = 1,
    popup = ~popup
  ) %>%
  addLegend(
    pal = pal_2021,
    values = ~Estimate,
    title = "Estimate"
  )

# --------------------
# 2022 DATA
# --------------------
mydata_2022 <- get_acs(
  geography = "congressional district",
  state = "TN",
  variables = VariableList,
  year = 2022,
  survey = "acs1",
  output = "wide",
  geometry = TRUE
)

mapdata_2022 <- mydata_2022 %>%
  rename(Area = NAME) %>%
  mutate(
    DistrictNumber = as.numeric(str_extract(Area, "\\d+")),
    Estimate = WhitePctE,
    Range = WhitePctM
  ) %>%
  st_as_sf() %>%
  st_transform(4326)

qs_2022 <- quantile(
  mapdata_2022$Estimate,
  probs = seq(0, 1, length.out = 6),
  na.rm = TRUE
)

pal_2022 <- colorBin(
  palette = "Blues",
  domain = mapdata_2022$Estimate,
  bins = qs_2022,
  pretty = FALSE
)

plotdf_2022 <- mapdata_2022 %>%
  st_drop_geometry() %>%
  mutate(
    point_color = pal_2022(Estimate),
    y_ordered = factor(DistrictNumber, levels = rev(district_order)),
    hover_text = paste0(
      "Area: ", Area,
      "<br>Estimate: ", round(Estimate, 1),
      "<br>Plus/Minus: ", round(Range, 1)
    )
  )

mygraph_2022 <- plot_ly(
  data = plotdf_2022,
  x = ~Estimate,
  y = ~y_ordered,
  type = "scatter",
  mode = "markers",
  showlegend = FALSE,
  marker = list(
    color = ~point_color,
    size = 8
  ),
  error_x = list(
    type = "data",
    array = ~Range,
    arrayminus = ~Range,
    visible = TRUE
  ),
  text = ~hover_text,
  hoverinfo = "text"
) %>%
  layout(
    title = list(
      text = "Estimates by area<br><sup>County subdivisions. Brackets show error margins.</sup>"
    ),
    xaxis = list(title = "ACS estimate"),
    yaxis = list(title = "")
  )

mapdata_2022$popup <- paste0(
  "<strong>", mapdata_2022$Area, "</strong><br/>",
  "<hr>",
  "Estimate: ", round(mapdata_2022$Estimate, 1), "<br/>",
  "Plus/Minus: ", round(mapdata_2022$Range, 1)
)

DivisionMap_2022 <- leaflet(mapdata_2022) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addPolygons(
    fillColor = ~pal_2022(Estimate),
    fillOpacity = 0.6,
    color = "black",
    weight = 1,
    popup = ~popup
  ) %>%
  addLegend(
    pal = pal_2022,
    values = ~Estimate,
    title = "Estimate"
  )

# --------------------
# DAVIDSON COUNTY SCATTERPLOT
# --------------------
davidson <- read.csv("https://github.com/drkblake/Data/raw/refs/heads/main/Davidson2024.csv")

model <- lm(Pct_Harris ~ Pct_Nonwhite, data = davidson)

trend_data <- davidson %>%
  arrange(Pct_Nonwhite) %>%
  mutate(predicted = predict(model, newdata = .))

Scatterplot <- plot_ly(
  data = davidson,
  x = ~Pct_Nonwhite,
  y = ~Pct_Harris,
  type = "scatter",
  mode = "markers"
) %>%
  add_trace(
    data = trend_data,
    x = ~Pct_Nonwhite,
    y = ~predicted,
    type = "scatter",
    mode = "lines",
    line = list(color = "black", width = 2),
    inherit = FALSE,
    showlegend = FALSE
  ) %>%
  layout(
    title = "Pct. for Harris by Pct. Nonwhite",
    xaxis = list(title = "Pct. Nonwhite"),
    yaxis = list(title = "Pct. for Harris")
  )