Overview

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.

Load Packages and Data

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

Calculate Distance From Home

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

Team comparison, 2025–2026

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

Percent of Roster From Home State

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

MTSU’s Top Feeder States, 2025–2026

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 Over Time, 2022–2026

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

Map of 2026 Player Hometowns

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)