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 | 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 |
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)