The MTSU area voted conspicuously Democratic in the last statewide election, producing margins more similar to neighboring Davidson County than to elsewhere in Rutherford County, an analysis shows.

The precinct that includes MTSU’s campus, 17-1, was one of only four Rutherford County precincts that Democratic candidate Jason Martin won during the 2022 race for Tennessee governor. Republican incumbent Gov. Bill Lee won all of the county’s remaining 29 precincts.


Rutherford precincts by winner, 2022 governor’s race

Martin polled best in central Murfreesboro and in Smyrna and LaVergne, although Lee still won most of the precincts in those areas.

Generally, Lee did best in the county’s whiter, wealthier suburban and rural areas, while Martin had his strongest showings in the county’s more diverse, poorer, and more densely populated spots.


Rutherford precincts, by percent for Lee, 2022 governor’s race

MTSU’s 17-1 precinct is among the county’s smaller precincts, with just over 1,500 voters casting ballots for either Martin (799) or Lee (705). Martin’s largest prize was Precinct 1-1, in extreme northwestern Rutherford. There, Martin received 1,002 votes to Lee’s 909.

By contrast, Lee won biggest in Southwestern Rutherford County’s Precinct 8-1, where he picked up 3,778 votes to Martin’s 1,235.


Rutherford precincts by total votes, votes for Lee, and votes for Martin

Martin’s campaign performed much better in neighboring Metro Nashville / Davidson County, where Martin won a majority of both votes and precincts.

Martin did especially well in the county’s central precincts, while Lee polled best in outlying precincts.


Nashville/Davidson precincts by winner, 2022 governor’s race
Nashville/Davidson precincts by percent for Lee, 2022 governor’s race

The full script - and a quick lesson on understanding R code.

Below is the R code that made the maps and graphic you see above. If it looks like gobbledygook, that’s OK. Trust that it will make more sense to you by the end of this course.

Meanwhile, look at this line of code, which appears early in the script. It imports the vote totals used in the analysis. It also illustrates the three main parts of just about every command in R. If you can learn how to spot each of those parts and understand what each one does, you’ll be halfway home.

RawElectionData <- read_xlsx("RawElectionData.xlsx", sheet = "SOFFICELso")

The first part of an R command essentially says, “Make this.” The second part says, “Then do this to it.” The third part says, “And do it in this exact way.” We call the first part the “object,” the second part the “function,” and the third part the “parameters.”

Here, the first part - the “object” - is RawElectionData. It’s a data structure called a “data frame” that will hold the election data the script will work with. Naming the data frame creates it. I could have named it anything. I picked “RawElectionData” because the name describes the data it will hold. The name will help me remember what’s in it.

The second part - the “function” - is read_xlsx(). The parentheses are part of the function. More in a minute about the stuff inside the parentheses. Like all other functions, read_xlsx tells R to do something. Specifically, read_xlsx() tells R to read data from an Excel-formatted spreadsheet file stored on your computer, and put that data into whatever object that <- is pointing to. In this case, of course, the object is RawElectionData.

R already knows how to do this import task. The read_xlsx() function merely tells R that it’s time to do that particular thing. It’s kind of like if I were to say, “Please raise your hand.” I wouldn’t have to tell to you things like how to get your brain to activate the particular sequence of muscle contractions involved in raising your hand. You would already know how to do all of that, and you would be able to do it automatically.

However, “Please raise your hand” is not very precise. It gives you no instructions about the manner in which to raise your hand, or even which hand to raise. If I wanted you to raise a specific hand in a specific fashion, I’d have to give you some specific directions, like, “Please raise your right hand, as high as you can, and as quickly as you can.”

The third part of an R command - the parameters - are like those specific directions. They give R specific directions on the manner in which to execute the function. Parameters go inside the function’s parentheses, and they’re usually separated from one another by commas. Here, the parameters are "RawElectionData.xlsx", sheet = "SOFFICELso". The "RawElectionData.xlsx" parameter gives R the name of the Excel spreadsheet file to import the data from. Spreadsheets sometimes store their data in two or more separate worksheets, or pages. The sheet = "SOFFICELso" parameter tells R which worksheet to draw from.

The quote marks around the spreadsheet and worksheet names are critically important. If you omit them, R will get confused and spit out some hard-to-decipher error message instead of doing what you wanted it to do. The same problems will arise if you omit one or more of the function’s parentheses, forget to include a comma, misspell the name of the function, get things out of order, and so on. In coding, punctuation, spelling and sequence matter. After all, if I said, “Please raise your quickly hand right, hi,” you probably wouldn’t be entirely sure what I was asking you do to.

When you want to figure out what a block of R code is written to accomplish, start by looking at the functions. They’ll tell you what the code is doing. If you don’t know what a function does, you can Google it. Searching for “R read_xlsx” will call up all sorts of pages explaining what read_xlsx does, and how you can use it.

Next, look at the objects. They’ll tell you what the code is making, especially if the programmer was nice enough to name objects descriptively. Finally, look at the function parameters. Sometimes, they can give you important clues about the code’s aims.

Comments can be really helpful, too, if the coder was nice enough to include them. A comment will begin with a # symbol, which tells R to ignore anything to the right of the #. That frees the coder to use plain English to tell a fellow human (you) what the code is doing.

Spacing and indentations technically don’t matter in R. Coders do use both, however, to try to make code neater and more readable by humans. For now, don’t worry about either. The R code editor we’ll be using handles indentation automatically. Also, if things get messy, there’s a button you can click to reformat your code beautifully.

Don’t expect this little tutorial to give you a full understanding of the R coding language. Even if English is your native tongue, you’re probably still learning how to use it correctly. It’s the same with R, which, really, is just another language. But I hope you’ve learned how to make sense out of some of the big patterns in R code. That’s a good start.

# Required packages

if (!require("tidyverse"))
  install.packages("tidyverse")
if (!require("mapview"))
  install.packages("mapview")
if (!require("sf"))
  install.packages("sf")
if (!require("leaflet"))
  install.packages("leaflet")
if (!require("leaflet.extras2"))
  install.packages("leaflet.extras2")
if (!require("plotly"))
  install.packages("plotly")

library(tidyverse)
library(mapview)
library(sf)
library(leaflet)
library(leafpop)
library(readxl)
library(plotly)

# Download and import election data
# from TN Secretary of State web site:
# https://sos.tn.gov/elections/results

download.file(
  "https://sos-prod.tnsosgovfiles.com/s3fs-public/document/20221108AllbyPrecinct.xlsx",
  "RawElectionData.xlsx",
  quiet = TRUE,
  mode = "wb"
)

RawElectionData <- read_xlsx("RawElectionData.xlsx", sheet = "SOFFICELso")

# Filter, calculate, and select
# to get data of interest
# then store results in MyData dataframe

MyData <- RawElectionData %>%
  filter(COUNTY == "Rutherford", CANDGROUP == "1") %>%
  mutate(
    Lee = PVTALLY1,
    Martin = PVTALLY2,
    Total = PVTALLY1 + PVTALLY2,
    Lee_Pct = round(PVTALLY1 / (PVTALLY1 + PVTALLY2), 2),
    Martin_Pct = round(PVTALLY2 / (PVTALLY1 + PVTALLY2), 2),
    Winner = case_when(
      PVTALLY1 > PVTALLY2 ~ "Lee (R)",
      PVTALLY2 > PVTALLY1 ~ "Martin (D)",
      .default = "Tie"
    )
  ) %>%
  select(COUNTY, PRECINCT, Total, Lee, Martin, Lee_Pct, Martin_Pct, Winner)

# Make a stacked bar chart

fig <- plot_ly(
  MyData,
  y = ~ reorder(PRECINCT, Total),
  x = ~ Lee,
  type = 'bar',
  orientation = "h",
  name = 'Lee',
  marker = list(color = 'red',
                line = list(color = 'ffffff',
                            width = 1)))

fig <- fig %>% add_trace(x = ~ Martin,
                         name = 'Martin',
                         marker = list(color = 'blue',
                                       line = list(color = 'ffffff',
                                                   width = 1)))

fig <- fig %>% 
  layout(barmode = "stack",
         xaxis = list(title = "Votes"),
         yaxis = list(title = "Precinct"))

fig


# Download and unzip a precinct map to pair with the vote data

download.file("https://github.com/drkblake/Data/raw/main/Voting_Precincts_5_31_24.zip","TNVotingPrecincts.zip")

unzip("TNVotingPrecincts.zip")

All_Precincts <- read_sf("Voting_Precincts_5_31_24.shp")

# Filter for particular county precincts

County_Precincts <- All_Precincts %>%
  filter(COUNTY == 149) %>%
  rename(PRECINCT = NEWVOTINGP)

# Merge election data and map file

MergeFile <- merge(MyData, County_Precincts, by = "PRECINCT", all.x = TRUE)

# Drop unneeded columns from MergeFile

MergeFile <- MergeFile %>%
  select(PRECINCT,
         Total,
         Lee,
         Martin,
         Lee_Pct,
         Martin_Pct,
         Winner,
         geometry)

# Format MergeFile as a map, and
# call the map MyMap

MyMap <- st_as_sf(MergeFile)

# Style and display the map

mypalette = colorRampPalette(c('red', 'blue'))

mapview(
  MyMap,
  zcol = "Winner",
  col.regions = mypalette,
  map.types = ("OpenStreetMap"),
  layer.name = "Winner",
  popup = popupTable(
    MyMap,
    feature.id = FALSE,
    row.numbers = FALSE,
    zcol = c(
      "PRECINCT",
      "Lee",
      "Martin",
      "Total",
      "Lee_Pct",
      "Martin_Pct",
      "Winner"
    )
  )
)

mypalette = colorRampPalette(c('blue', 'red'))

mapview(
  MyMap,
  zcol = "Lee_Pct",
  col.regions = mypalette, at = seq(0, 1, .2),
  map.types = ("OpenStreetMap"),
  layer.name = "Pct. for Lee",
  popup = popupTable(
    MyMap,
    feature.id = FALSE,
    row.numbers = FALSE,
    zcol = c(
      "PRECINCT",
      "Lee",
      "Martin",
      "Total",
      "Lee_Pct",
      "Martin_Pct",
      "Winner"
    )
  )
)

# Davidsion County analysis

MyData <- RawElectionData %>%
  filter(COUNTY == "Davidson", CANDGROUP == "1") %>%
  mutate(
    Lee = PVTALLY1,
    Martin = PVTALLY2,
    Total = PVTALLY1 + PVTALLY2,
    Lee_Pct = round(PVTALLY1 / (PVTALLY1 + PVTALLY2), 2),
    Martin_Pct = round(PVTALLY2 / (PVTALLY1 + PVTALLY2), 2),
    Winner = case_when(
      PVTALLY1 > PVTALLY2 ~ "Lee (R)",
      PVTALLY2 > PVTALLY1 ~ "Martin (D)",
      .default = "Tie"
    )
  ) %>%
  select(COUNTY, PRECINCT, Total, Lee, Martin, Lee_Pct, Martin_Pct, Winner)

# Make a stacked bar chart

# Make a stacked bar chart

fig <- plot_ly(
  MyData,
  y = ~ reorder(PRECINCT, Total),
  x = ~ Lee,
  type = 'bar',
  orientation = "h",
  name = 'Lee',
  marker = list(color = 'red',
                line = list(color = 'ffffff',
                            width = 0)))

fig <- fig %>% add_trace(x = ~ Martin,
                         name = 'Martin',
                         marker = list(color = 'blue',
                                       line = list(color = 'ffffff',
                                                   width = 0)))

fig <- fig %>% 
  layout(barmode = "stack",
         xaxis = list(title = "Votes"),
         yaxis = list(title = "Precinct"))

fig

download.file("https://github.com/drkblake/Data/raw/main/Davidson_County_Voting_Precincts.zip","DavidsonVotingPrecincts")

unzip("DavidsonVotingPrecincts")

County_Precincts <- read_sf("Davidson_County_Voting_Precincts.shp")

# Deleting leading zeroes from MyData precinct codes
# so they will match the precinct codes in
# County_Precincts

MyData$PRECINCT <- sub("^0+", "", MyData$PRECINCT)

# Renaming County_Precincts precinct code column as
# PRECINCT to match precinct code column name in MyData

County_Precincts <- County_Precincts %>%
  rename(PRECINCT = Precinct)

# Merge election data and map file

MergeFile <- merge(MyData, County_Precincts, by = "PRECINCT", all.x = TRUE)

# Drop unneeded columns from MergeFile

MergeFile <- MergeFile %>%
  select(PRECINCT,
         Total,
         Lee,
         Martin,
         Lee_Pct,
         Martin_Pct,
         Winner,
         geometry)

# Format MergeFile as a map, and
# call the map MyMap

MyMap <- st_as_sf(MergeFile)

# Style and display the map

mypalette = colorRampPalette(c('red', 'blue'))

mapview(
  MyMap,
  zcol = "Winner",
  col.regions = mypalette,
  map.types = ("OpenStreetMap"),
  layer.name = "Winner",
  popup = popupTable(
    MyMap,
    feature.id = FALSE,
    row.numbers = FALSE,
    zcol = c(
      "PRECINCT",
      "Lee",
      "Martin",
      "Total",
      "Lee_Pct",
      "Martin_Pct",
      "Winner"
    )
  )
)

mypalette = colorRampPalette(c('blue', 'red'))

mapview(
  MyMap,
  zcol = "Lee_Pct",
  col.regions = mypalette, at = seq(0, 1, .2),
  map.types = ("OpenStreetMap"),
  layer.name = "Pct. for Lee",
  popup = popupTable(
    MyMap,
    feature.id = FALSE,
    row.numbers = FALSE,
    zcol = c(
      "PRECINCT",
      "Lee",
      "Martin",
      "Total",
      "Lee_Pct",
      "Martin_Pct",
      "Winner"
    )
  )
)