Tennessee Midstate Population Analysis, By Density

Davidson and Rutherford counties lead the “midstate” in population density, based on an analysis of each county’s square miles and population counts using data obtained by the U.S. Census Bureau and its American Community Survey. The population density table below ranks counties in Middle Tennessee in the Nashville Metropolitan Statistical Area.

A “high density” county is one where there are at least 500 residents per square mile. A “medium density” county has 100-499 residents per square mile. And finally, a “low density” county has fewer than 100 residents per square mile.

The table shows Davidson County, home of the city of Nashville, is the most populated in the midstate and the most dense, followed by Rutherford County, which is home of the state’s geographic center and Middle Tennessee State University. Both Davidson and Rutherford rank in the High Density category. The other “doughnut counties” — those that share a border with Davidson County — all rank in the Medium Density category. The other midstate counties are a mixture of Medium and Low Density.

County Population, Change, and Density
County Current Population Earlier Population Region Change Percent Change (Proportion) Square Miles Density (People per Square Mile) Density Category
Davidson 715,388 598,184 Davidson 117,204 0.1959 504 1,419.42063 High Density
Rutherford 360,646 292,425 Doughnut 68,221 0.2333 620 581.68710 High Density
Williamson 260,351 208,242 Doughnut 52,109 0.2502 583 446.57118 Medium Density
Sumner 204,424 174,773 Doughnut 29,651 0.1697 529 386.43478 Medium Density
Wilson 158,805 129,918 Doughnut 28,887 0.2223 571 278.11734 Medium Density
Maury 107,791 88,738 Non-doughnut 19,053 0.2147 613 175.84176 Medium Density
Robertson 75,539 67,517 Doughnut 8,022 0.1188 476 158.69538 Medium Density
Cheatham 41,829 39,087 Doughnut 2,742 0.0702 302 138.50662 Medium Density
Dickson 55,983 51,608 Non-doughnut 4,375 0.0848 490 114.25102 Medium Density
Trousdale 11,957 10,131 Non-doughnut 1,826 0.1802 114 104.88596 Medium Density
Macon 26,240 23,261 Non-doughnut 2,979 0.1281 307 85.47231 Low Density
Smith 20,389 19,389 Non-doughnut 1,000 0.0516 314 64.93312 Low Density
Cannon 14,818 13,958 Non-doughnut 860 0.0616 266 55.70677 Low Density
Hickman 25,436 24,561 Non-doughnut 875 0.0356 612 41.56209 Low Density

Code

The following code produces the population density analysis and tables.

# =============================================================================
# Step 1: Load required packages
# =============================================================================

library(tidyverse)
library(knitr)
library(kableExtra)


# =============================================================================
# Step 2: Create a vector of county names
# =============================================================================

County <- c(
  "Cannon", "Cheatham", "Davidson", "Dickson", "Hickman", "Macon",
  "Maury", "Robertson", "Rutherford", "Smith", "Sumner", "Trousdale",
  "Williamson", "Wilson"
)


# =============================================================================
# Step 3: Create a vector of current population values
# =============================================================================

CurrentPop <- c(
  14818, 41829, 715388, 55983, 25436, 26240, 107791,
  75539, 360646, 20389, 204424, 11957, 260351, 158805
)


# =============================================================================
# Step 4: Create a vector of earlier population values
# =============================================================================

EarlierPop <- c(
  13958, 39087, 598184, 51608, 24561, 23261, 88738,
  67517, 292425, 19389, 174773, 10131, 208242, 129918
)


# =============================================================================
# Step 5: Assign each county to a region
# =============================================================================

Region <- c(
  "Non-doughnut", "Doughnut", "Davidson", "Non-doughnut",
  "Non-doughnut", "Non-doughnut", "Non-doughnut", "Doughnut",
  "Doughnut", "Non-doughnut", "Doughnut", "Non-doughnut",
  "Doughnut", "Doughnut"
)


# =============================================================================
# Step 6: Combine the vectors into the Population data frame
# =============================================================================

Population <- data.frame(
  County,
  CurrentPop,
  EarlierPop,
  Region
)


# =============================================================================
# Step 7: Calculate population change
# =============================================================================

Population <- Population %>%
  mutate(Change = CurrentPop - EarlierPop)


# =============================================================================
# Step 8: Sort Population by current population in descending order
# =============================================================================

Population <- Population %>%
  arrange(desc(CurrentPop))


# =============================================================================
# Step 9: Display the Population data frame
# =============================================================================

print(Population)


# =============================================================================
# Step 10: Create Population_v2 and calculate percentage change
# =============================================================================

# Percent_change is stored as a proportion: 0.20 represents 20%.
Population_v2 <- Population %>%
  mutate(Percent_change = Change / EarlierPop)


# =============================================================================
# Step 11: Sort Population_v2 by percentage change in descending order
# =============================================================================

Population_v2 <- Population_v2 %>%
  arrange(desc(Percent_change))


# =============================================================================
# Step 12: Download Tennessee county land-area data
# =============================================================================

TN_Counties <- read_delim(
  "https://www2.census.gov/geo/docs/maps-data/data/gazetteer/2025_Gazetteer/2025_gaz_counties_47.txt",
  delim = "|",
  trim_ws = TRUE,
  show_col_types = FALSE
)


# =============================================================================
# Step 13: Create the Land_Area data frame
# =============================================================================

# Remove the " County" suffix and round area to whole square miles.
Land_Area <- TN_Counties %>%
  transmute(
    County = str_remove(NAME, " County$"),
    Square_Miles = round(ALAND_SQMI)
  )


# =============================================================================
# Step 14: Display the Land_Area data frame
# =============================================================================

print(Land_Area)


# =============================================================================
# Step 15: Add Square_Miles to Population_v2 by matching County
# =============================================================================

Population_v2 <- Population_v2 %>%
  left_join(
    Land_Area %>% select(County, Square_Miles),
    by = "County"
  )


# =============================================================================
# Step 16: Calculate population density
# =============================================================================

# Density is the number of people per square mile.
Population_v2 <- Population_v2 %>%
  mutate(Density = CurrentPop / Square_Miles)


# =============================================================================
# Step 17: Create density categories
# =============================================================================

# Medium Density includes values from 100 up to, but below, 500.
Population_v2 <- Population_v2 %>%
  mutate(
    Density_Category = case_when(
      Density >= 500 ~ "High Density",
      Density >= 100 ~ "Medium Density",
      Density < 100 ~ "Low Density",
      TRUE ~ NA_character_
    )
  )


# =============================================================================
# Step 18: Sort Population_v2 by density in descending order
# =============================================================================

Population_v2 <- Population_v2 %>%
  arrange(desc(Density))


# =============================================================================
# Step 19: Display Population_v2 as text during interactive use
# =============================================================================

print(Population_v2)


# =============================================================================
# Step 20: Create the formatted Population table
# =============================================================================

Population_table <- knitr::kable(
  Population,
  format = "html",
  caption = "County Population Data",
  row.names = FALSE,
  digits = 0,
  col.names = c(
    "County",
    "Current Population",
    "Earlier Population",
    "Region",
    "Change"
  ),
  format.args = list(big.mark = ",")
) %>%
  kableExtra::kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width = FALSE,
    position = "center"
  )


# =============================================================================
# Step 21: Display the formatted Population table
# =============================================================================

print(Population_table)


# =============================================================================
# Step 22: Create the formatted Population_v2 table
# =============================================================================

# Select columns explicitly to match the nine labels and digit settings.
Population_v2_table <- Population_v2 %>%
  select(
    County,
    CurrentPop,
    EarlierPop,
    Region,
    Change,
    Percent_change,
    Square_Miles,
    Density,
    Density_Category
  ) %>%
  knitr::kable(
    format = "html",
    caption = "County Population, Change, and Density",
    row.names = FALSE,
    digits = c(0, 0, 0, 0, 0, 4, 0, 5, 0),
    col.names = c(
      "County",
      "Current Population",
      "Earlier Population",
      "Region",
      "Change",
      "Percent Change (Proportion)",
      "Square Miles",
      "Density (People per Square Mile)",
      "Density Category"
    ),
    format.args = list(big.mark = ",")
  ) %>%
  kableExtra::kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width = FALSE,
    position = "center"
  )


# =============================================================================
# Step 23: Display the formatted Population_v2 table
# =============================================================================

print(Population_v2_table)