Introduction

Axios recently noted that more U.S. counties are majority-minority than people tend to assume, and that a good share of them sit outside metro areas. Worth a look with our own tools – and worth extending back a decade, to see how the metro/nonmetro split has moved over time.


Data

County population by race and Hispanic origin comes from the American Community Survey 5-year estimates, pulled via tidycensus. A county is majority-minority when its non-Hispanic White alone population falls under 50%.

We compare two non-overlapping 5-year windows exactly a decade apart – 2010-2014 and 2020-2024 – rather than adjacent windows, which would share most of their survey years and mostly show noise. Metro status comes from USDA ERS’s Rural-Urban Continuum Codes (RUCC), collapsed to metro (codes 1-3) vs. nonmetro (codes 4-9). We use the vintage matched to each ACS window – 2013 RUCC codes (the nearest available) for the 2014 data, 2023 RUCC codes for the 2024 data – since county metro status itself changes over a decade.

pacman::p_load(tidycensus, dplyr, tidyr, ggplot2, sf, readxl, tigris, DT)
get_race <- function(yr, geometry = FALSE) {
  get_acs(
    geography = "county",
    variables = c(total = "B03002_001", nh_white = "B03002_003"),
    year = yr,
    survey = "acs5",
    output = "wide",
    geometry = geometry
  ) |>
    mutate(
      pct_nh_white = nh_whiteE / totalE,
      majority_minority = pct_nh_white < 0.5
    )
}

race_2014 <- get_race(2014)
race_2024 <- get_race(2024, geometry = TRUE)
rucc_2013_path <- tempfile(fileext = ".xls")
download.file("https://www.ers.usda.gov/media/5769/2013-rural-urban-continuum-codes.xls",
              rucc_2013_path, mode = "wb", quiet = TRUE)

rucc_2023_path <- tempfile(fileext = ".xlsx")
download.file("https://www.ers.usda.gov/media/5767/2023-rural-urban-continuum-codes.xlsx",
              rucc_2023_path, mode = "wb", quiet = TRUE)

rucc_2014 <- read_excel(rucc_2013_path) |>
  transmute(GEOID = as.character(FIPS),
            metro_2014 = if_else(RUCC_2013 <= 3, "Metro", "Nonmetro"))

rucc_2024 <- read_excel(rucc_2023_path) |>
  transmute(GEOID = as.character(FIPS),
            metro_status = if_else(RUCC_2023 <= 3, "Metro", "Nonmetro"))

counties <- race_2024 |>
  left_join(rucc_2024, by = "GEOID") |>
  left_join(race_2014 |> st_drop_geometry() |> select(GEOID, mm_2014 = majority_minority),
            by = "GEOID") |>
  left_join(rucc_2014, by = "GEOID") |>
  filter(!is.na(metro_status), !is.na(mm_2014), !is.na(metro_2014))

Then vs. Now

bind_rows(
  counties |> st_drop_geometry() |> filter(mm_2014) |>
    count(metro_status = metro_2014) |> mutate(year = "2014"),
  counties |> st_drop_geometry() |> filter(majority_minority) |>
    count(metro_status) |> mutate(year = "2024")
) |>
  pivot_wider(names_from = year, values_from = n) |>
  rename(`Metro status` = metro_status) |>
  DT::datatable(rownames = FALSE, options = list(dom = 't'),
                caption = "Majority-minority counties, 2014 vs. 2024 (metro status matched to each year)")

Nonmetro counties actually led in 2014 – 219 to 211. By 2024 metro had pulled ahead, 257 to 240. Part of that shift is racial composition changing; part of it is counties themselves crossing the metro/nonmetro line over the decade, independent of demographics:

counties |>
  st_drop_geometry() |>
  count(`2014 status` = metro_2014, `2024 status` = metro_status, name = "Counties") |>
  DT::datatable(rownames = FALSE, options = list(dom = 't'),
                caption = "How county metro status itself changed, 2014 to 2024")

Where It Stands Now

counties_shifted <- shift_geometry(counties) |>
  mutate(
    map_cat = case_when(
      metro_status == "Metro" ~ "Metro",
      majority_minority ~ "Rural, minority",
      TRUE ~ "Rural, white"
    )
  )
states_shifted <- shift_geometry(states(cb = TRUE, resolution = "20m"))
ggplot() +
  geom_sf(data = filter(counties_shifted, map_cat == "Metro"),
          fill = "grey90", color = NA) +
  geom_sf(data = filter(counties_shifted, map_cat != "Metro"),
          aes(fill = map_cat), color = "grey30", linewidth = 0.1) +
  geom_sf(data = states_shifted, fill = NA, color = "white", linewidth = 0.3) +
  scale_fill_manual(
    values = c("Rural, white" = "grey80", "Rural, minority" = "#fc8d62"),
    name = NULL
  ) +
  labs(title = "Rural Counties: Majority-White vs. Majority-Minority",
       subtitle = "Metro counties shown in light grey -- ACS 2020-2024") +
  theme_void() +
  theme(plot.title = element_text(size = 16), legend.position = "top")


Summary

Majority-minority counties grew from 430 to 497 over the decade. Nonmetro counties held the lead in 2014; metro counties have it now, a flip driven partly by demographic change and partly by counties changing metro classification altogether – 72 counties moved from nonmetro to metro over the period, more than the 55 that moved the other way. Our numbers won’t match Axios’s precisely; different data source, different vintage, different county classification.