In my view, the maps and charts show that the border changes from 2021 to 2022 have treated minority voters unfairly. In both maps, you can see that the areas with fewer white people have fewer voting districts than areas with the highest white percentage. You can also see very clearly in the Estimates by area graphs that areas with larger minority populations have less area. This suggests that the areas with lower white populations tend to have less voting and representation, which is unfair.
It’s also evident in the Scatter plot below that the number of votes for Kamala Harris in 2024 was higher in districts with higher proportions of non-white residents. Having less representation and smaller districts in minority areas will affect voting and representation for minorities. A three-judge panel may not have found the redistricting illegal, but I believe there is definitely evidence of unequal and unfair treatment towards the minority populations in Tennessee.
# ----------------------------------------------------------
# Step 1: Install required packages (if missing)
# ----------------------------------------------------------
if (!require("tidyverse"))
install.packages("tidyverse")
if (!require("tidycensus"))
install.packages("tidycensus")
if (!require("sf"))
install.packages("sf")
if (!require("leaflet"))
install.packages("leaflet")
if (!require("htmlwidgets"))
install.packages("htmlwidgets")
if (!require("plotly"))
install.packages("plotly") # For the interactive dot plot
# ----------------------------------------------------------
# Step 2: Load libraries
# ----------------------------------------------------------
library(tidyverse)
library(tidycensus)
library(sf)
library(leaflet)
library(htmlwidgets)
library(plotly)
# ----------------------------------------------------------
# Step 3: Transmit Census API key (uncomment and paste yours)
# ----------------------------------------------------------
census_api_key("d19e339dbdd00abb0e351a5b00bb313f91080401")
# ----------------------------------------------------------
# Step 4: Fetch ACS codebooks (for variable lookup if needed)
# ----------------------------------------------------------
DetailedTables <- load_variables(2024, "acs5", cache = TRUE)
SubjectTables <- load_variables(2024, "acs5/subject", cache = TRUE)
ProfileTables <- load_variables(2024, "acs5/profile", cache = TRUE)
# ----------------------------------------------------------
# Step 5: Specify target variable(s)
# ----------------------------------------------------------
VariableList <- c(Estimate_ = "DP05_0037P")
# ----------------------------------------------------------
# Step 6: Fetch ACS data (congressional district, Tennessee)
# ----------------------------------------------------------
mydata <- get_acs(
geography = "congressional district",
state = c ("TN"),
variables = VariableList,
year = 2021,
survey = "acs1",
output = "wide",
geometry = TRUE
)
# ----------------------------------------------------------
# Step 7: Reformat the NAME field into Area / County / State
# ----------------------------------------------------------
mydata <- separate_wider_delim(
mydata,
NAME,
delim = ", ",
names = c("Area", "State")
)
# ----------------------------------------------------------
# Step 8: Filter to Rutherford County
# ----------------------------------------------------------
filtereddata <- mydata %>%
filter(State %in% c("Tennessee"))
# ----------------------------------------------------------
# Step 9: Prepare data for mapping (rename, as sf, CRS)
# ----------------------------------------------------------
mapdata <- filtereddata %>%
rename(
Estimate = Estimate_E,
Range = Estimate_M
) %>%
st_as_sf()
# Ensure CRS is WGS84 for Leaflet
mapdata <- st_transform(mapdata, 4326)
# ----------------------------------------------------------
# Step 10: Build color palette with quantile-based breaks
# ----------------------------------------------------------
qs <- quantile(mapdata$Estimate, probs = seq(0, 1, length.out = 6), na.rm = TRUE)
pal <- colorBin(
palette = "BuPu", # Can specify other palettes here
domain = mapdata$Estimate,
bins = qs,
pretty = FALSE
)
# ----------------------------------------------------------
# Step 11: Build the plotly dot plot with error bars
# ----------------------------------------------------------
# Add point color from the same Leaflet palette and ordered y factor
filtereddata <- filtereddata %>%
mutate(
point_color = pal(Estimate_E),
y_ordered = reorder(Area, Estimate_E),
hover_text = dplyr::if_else(
!is.na(Area),
paste0("Area: ", Area),
Area
)
)
# Create the plotly scatter with horizontal error bars and thin gray borders
mygraph <- plot_ly(
data = filtereddata,
x = ~Estimate_E,
y = ~y_ordered,
type = "scatter",
mode = "markers",
marker = list(
color = ~point_color,
size = 8,
line = list(
color = "rgba(120,120,120,0.9)", # thin gray border for contrast
width = 0.5
)
),
error_x = list(
type = "data",
array = ~Estimate_M, # + side
arrayminus = ~Estimate_M, # - side
color = "rgba(0,0,0,0.65)",
thickness = 1
),
text = ~hover_text,
# Show District (from hover_text) and the X value with thousands separators
hovertemplate = "%{text}<br>%{x:,}<extra></extra>"
) %>%
layout(
title = list(text = "Estimates by area<br><sup>County subdivisions. Brackets show error margins.</sup>"),
xaxis = list(title = "ACS estimate"),
yaxis = list(title = "")
)
# display the plot
mygraph
# ----------------------------------------------------------
# Step 12: Create popup content for the map
# ----------------------------------------------------------
mapdata$popup <- paste0(
"<strong>", mapdata$Area, "</strong><br/>",
"<hr>",
"Estimate: ", format(mapdata$Estimate, big.mark = ","), "<br/>",
"Plus/Minus: ", format(mapdata$Range, big.mark = ",")
)
# ----------------------------------------------------------
# Step 13: Build the Leaflet map
# ----------------------------------------------------------
DivisionMap <- leaflet(mapdata) %>%
# Choose one basemap:
addProviderTiles(providers$CartoDB.Positron) %>%
# addProviderTiles(providers$Esri.WorldStreetMap, group = "Streets (Esri World Street Map)") %>%
# addProviderTiles(providers$Esri.WorldImagery, group = "Satellite (Esri World Imagery)") %>%
addPolygons(
fillColor = ~pal(Estimate),
fillOpacity = 0.5,
color = "black",
weight = 1,
popup = ~popup
) %>%
addLegend(
pal = pal,
values = ~Estimate,
title = "Estimate",
labFormat = labelFormat(big.mark = ",")
)
DivisionMap
# ----------------------------------------------------------
# Step 14: Export graph as a standalone HTML file
# ----------------------------------------------------------
# This creates a fully self-contained HTML file for the dot plot.
saveWidget(
widget = as_widget(mygraph),
file = "ACSGraph.html",
selfcontained = TRUE
)
# ----------------------------------------------------------
# Step 15: Export map as a standalone HTML file
# ----------------------------------------------------------
# This creates a fully self-contained HTML you can open or share.
# Adjust the path/filename as you like.
saveWidget(
widget = DivisionMap,
file = "ACSMap.html",
selfcontained = TRUE
)
# ----------------------------------------------------------
# Step 1: Install required packages (if missing)
# ----------------------------------------------------------
if (!require("tidyverse"))
install.packages("tidyverse")
if (!require("tidycensus"))
install.packages("tidycensus")
if (!require("sf"))
install.packages("sf")
if (!require("leaflet"))
install.packages("leaflet")
if (!require("htmlwidgets"))
install.packages("htmlwidgets")
if (!require("plotly"))
install.packages("plotly") # For the interactive dot plot
# ----------------------------------------------------------
# Step 2: Load libraries
# ----------------------------------------------------------
library(tidyverse)
library(tidycensus)
library(sf)
library(leaflet)
library(htmlwidgets)
library(plotly)
# ----------------------------------------------------------
# Step 3: Transmit Census API key (uncomment and paste yours)
# ----------------------------------------------------------
census_api_key("d19e339dbdd00abb0e351a5b00bb313f91080401")
# ----------------------------------------------------------
# Step 4: Fetch ACS codebooks (for variable lookup if needed)
# ----------------------------------------------------------
DetailedTables <- load_variables(2024, "acs5", cache = TRUE)
SubjectTables <- load_variables(2024, "acs5/subject", cache = TRUE)
ProfileTables <- load_variables(2024, "acs5/profile", cache = TRUE)
# ----------------------------------------------------------
# Step 5: Specify target variable(s)
# ----------------------------------------------------------
VariableList <- c(Estimate_ = "DP05_0037P")
# ----------------------------------------------------------
# Step 6: Fetch ACS data (congressional district, Tennessee)
# ----------------------------------------------------------
mydata <- get_acs(
geography = "congressional district",
state = c ("TN"),
variables = VariableList,
year = 2022,
survey = "acs1",
output = "wide",
geometry = TRUE
)
# ----------------------------------------------------------
# Step 7: Reformat the NAME field into Area / County / State
# ----------------------------------------------------------
mydata <- separate_wider_delim(
mydata,
NAME,
delim = ", ",
names = c("Area", "State")
)
# ----------------------------------------------------------
# Step 8: Filter to Rutherford County
# ----------------------------------------------------------
filtereddata <- mydata %>%
filter(State %in% c("Tennessee"))
# ----------------------------------------------------------
# Step 9: Prepare data for mapping (rename, as sf, CRS)
# ----------------------------------------------------------
mapdata <- filtereddata %>%
rename(
Estimate = Estimate_E,
Range = Estimate_M
) %>%
st_as_sf()
# Ensure CRS is WGS84 for Leaflet
mapdata <- st_transform(mapdata, 4326)
# ----------------------------------------------------------
# Step 10: Build color palette with quantile-based breaks
# ----------------------------------------------------------
qs <- quantile(mapdata$Estimate, probs = seq(0, 1, length.out = 6), na.rm = TRUE)
pal <- colorBin(
palette = "Blues", # Can specify other palettes here
domain = mapdata$Estimate,
bins = qs,
pretty = FALSE
)
# ----------------------------------------------------------
# Step 11: Build the plotly dot plot with error bars
# ----------------------------------------------------------
# Add point color from the same Leaflet palette and ordered y factor
filtereddata <- filtereddata %>%
mutate(
point_color = pal(Estimate_E),
y_ordered = reorder(Area, Estimate_E),
hover_text = dplyr::if_else(
!is.na(Area),
paste0("Area: ", Area),
Area
)
)
# Create the plotly scatter with horizontal error bars and thin gray borders
mygraph <- plot_ly(
data = filtereddata,
x = ~Estimate_E,
y = ~y_ordered,
type = "scatter",
mode = "markers",
marker = list(
color = ~point_color,
size = 8,
line = list(
color = "rgba(120,120,120,0.9)", # thin gray border for contrast
width = 0.5
)
),
error_x = list(
type = "data",
array = ~Estimate_M, # + side
arrayminus = ~Estimate_M, # - side
color = "rgba(0,0,0,0.65)",
thickness = 1
),
text = ~hover_text,
# Show District (from hover_text) and the X value with thousands separators
hovertemplate = "%{text}<br>%{x:,}<extra></extra>"
) %>%
layout(
title = list(text = "Estimates by area<br><sup>County subdivisions. Brackets show error margins.</sup>"),
xaxis = list(title = "ACS estimate"),
yaxis = list(title = "")
)
# display the plot
mygraph
# ----------------------------------------------------------
# Step 12: Create popup content for the map
# ----------------------------------------------------------
mapdata$popup <- paste0(
"<strong>", mapdata$Area, "</strong><br/>",
"<hr>",
"Estimate: ", format(mapdata$Estimate, big.mark = ","), "<br/>",
"Plus/Minus: ", format(mapdata$Range, big.mark = ",")
)
# ----------------------------------------------------------
# Step 13: Build the Leaflet map
# ----------------------------------------------------------
DivisionMap <- leaflet(mapdata) %>%
# Choose one basemap:
addProviderTiles(providers$CartoDB.Positron) %>%
# addProviderTiles(providers$Esri.WorldStreetMap, group = "Streets (Esri World Street Map)") %>%
# addProviderTiles(providers$Esri.WorldImagery, group = "Satellite (Esri World Imagery)") %>%
addPolygons(
fillColor = ~pal(Estimate),
fillOpacity = 0.5,
color = "black",
weight = 1,
popup = ~popup
) %>%
addLegend(
pal = pal,
values = ~Estimate,
title = "Estimate",
labFormat = labelFormat(big.mark = ",")
)
DivisionMap
mydata <- read.csv("https://github.com/drkblake/Data/raw/refs/heads/main/Davidson2024.csv")
Scatterplot <- plot_ly(
data = mydata,
x = ~Pct_Nonwhite,
y = ~Pct_Harris,
type = "scatter",
mode = "markers",
text = ~Precinct,
hoverinfo = "text+x+y",
marker = list(
color= "#873260",
size = 8,
opacity = 0.7
)
) %>%
add_trace(
type = "scatter",
mode = "lines",
x = ~Pct_Nonwhite,
y = fitted(lm(Pct_Harris ~ Pct_Nonwhite, data = mydata)),
name = "OLS trend",
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")
)
Scatterplot