This report compares where MTSU Football’s players come from with the other nine Conference USA programs. The data are team rosters (2022–2026) from the College Football Data API, hosted on GitHub. Because several programs have incomplete rosters for 2023–2024, the team comparisons below use the 2025 and 2026 seasons.
library(tidyverse)
library(geosphere)
library(DT)
library(plotly)
library(leaflet)
base_url <- "https://raw.githubusercontent.com/johndlafrance-png/cusa-hometowns/main/data/"
rosters <- read_csv(
paste0(base_url, "cusa_rosters.csv"),
col_types = cols(athlete_id = col_character(),
home_county_fips = col_character())
)
campuses <- read_csv(paste0(base_url, "cusa_campuses.csv"))
Each player is joined to his team’s stadium location. The code then calculates the straight-line distance in miles from the player’s hometown to campus and flags whether he is from the same state as his school.
players <- rosters |>
left_join(campuses |>
select(school, campus_state = state,
campus_lat = latitude, campus_lon = longitude),
by = c("team" = "school")) |>
mutate(
miles_from_home = distHaversine(
cbind(home_longitude, home_latitude),
cbind(campus_lon, campus_lat)
) / 1609.34,
in_state = home_state == campus_state,
is_mtsu = team == "Middle Tennessee"
)
# Recent seasons, players with a known hometown only
recent <- players |>
filter(season %in% c(2025, 2026), !is.na(home_latitude))
The table shows, for each team: how many player-seasons have a known hometown, the percent from the school’s home state, the percent from within 300 miles of campus, and the median distance from home.
team_summary <- recent |>
group_by(team) |>
summarise(
players = n(),
pct_in_state = round(mean(in_state, na.rm = TRUE) * 100, 1),
pct_within_300 = round(mean(miles_from_home <= 300) * 100, 1),
median_miles = round(median(miles_from_home)),
.groups = "drop"
) |>
arrange(desc(pct_in_state))
datatable(team_summary,
colnames = c("Team", "Player-seasons", "% in-state",
"% within 300 mi", "Median miles from home"),
rownames = FALSE,
options = list(pageLength = 10, dom = "t"))
MTSU is highlighted in blue. Hover over a bar to see the exact value.
chart_data <- team_summary |>
mutate(team = fct_reorder(team, pct_in_state),
group = if_else(team == "Middle Tennessee", "MTSU", "Other CUSA"))
p <- ggplot(chart_data,
aes(x = pct_in_state, y = team, fill = group,
text = paste0(team, ": ", pct_in_state, "% in-state"))) +
geom_col() +
scale_fill_manual(values = c("MTSU" = "#0066CC", "Other CUSA" = "grey70")) +
labs(x = "% of players from the school's home state", y = NULL, fill = NULL) +
theme_minimal()
ggplotly(p, tooltip = "text")
recent |>
filter(is_mtsu) |>
count(home_state, sort = TRUE, name = "players") |>
mutate(pct = round(players / sum(players) * 100, 1)) |>
head(10) |>
datatable(colnames = c("Home state", "Player-seasons", "% of MTSU roster"),
rownames = FALSE,
options = list(dom = "t"))
MTSU has complete rosters for every season, so its trend can be shown across all five years.
players |>
filter(is_mtsu, !is.na(home_latitude)) |>
group_by(season) |>
summarise(
players = n(),
pct_in_state = round(mean(in_state, na.rm = TRUE) * 100, 1),
median_miles = round(median(miles_from_home)),
.groups = "drop"
) |>
datatable(colnames = c("Season", "Players", "% in-state",
"Median miles from home"),
rownames = FALSE,
options = list(dom = "t"))
Each dot is a player on a 2026 CUSA roster. Blue dots are MTSU players; gray dots are players from the other nine teams. Click a dot to see the player’s name, team and hometown.
map_data <- players |>
filter(season == 2026, !is.na(home_latitude)) |>
arrange(is_mtsu) # draw MTSU dots on top
leaflet(map_data) |>
addProviderTiles(providers$Esri.WorldGrayCanvas) |>
addCircleMarkers(
lng = ~home_longitude, lat = ~home_latitude,
radius = ~if_else(is_mtsu, 5, 3),
color = ~if_else(is_mtsu, "#0066CC", "grey"),
stroke = FALSE, fillOpacity = 0.7,
popup = ~paste0("<b>", first_name, " ", last_name, "</b><br>",
team, " (", position, ")<br>",
home_city, ", ", home_state)
) |>
setView(lng = -90, lat = 35, zoom = 4)