Skip to contents
theme_readme <- function() {
  theme_minimal(base_size = 14) +
    theme(
      plot.title = element_text(face = "bold", size = 16),
      plot.subtitle = element_text(color = "gray40"),
      panel.grid.minor = element_blank(),
      legend.position = "bottom"
    )
}

colors <- c("total" = "#2C3E50", "white" = "#3498DB", "black" = "#E74C3C",
            "hispanic" = "#F39C12", "asian" = "#9B59B6", "native_american" = "#1ABC9C",
            "pacific_islander" = "#E67E22", "multiracial" = "#95A5A6")

1. Detroit’s collapse is staggering

Detroit Public Schools has lost over 100,000 students since 2000. The district went from 154,648 students in 2000 (as Detroit City School District) to 48,117 in 2025 (as Detroit Public Schools Community District after a 2016 restructuring). This represents one of the most dramatic urban enrollment declines in American education history.

library(mischooldata)
library(dplyr)

# Note: Detroit district ID changed from 82010 to 82015 in 2016 restructuring
enr_long <- fetch_enr_multi(c(2000, 2005, 2010, 2020, 2025), use_cache = TRUE)

detroit <- enr_long %>%
  filter(is_district,
         grepl("Detroit City|Detroit Public Schools Community", district_name),
         subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  select(end_year, district_name, n_students)
stopifnot(nrow(detroit) > 0)
detroit
#>   end_year                             district_name n_students
#> 1     2000              Detroit City School District     154648
#> 2     2005              Detroit City School District     141406
#> 3     2010              Detroit City School District      87877
#> 4     2020 Detroit Public Schools Community District      50016
#> 5     2025 Detroit Public Schools Community District      48117
print(detroit)
#>   end_year                             district_name n_students
#> 1     2000              Detroit City School District     154648
#> 2     2005              Detroit City School District     141406
#> 3     2010              Detroit City School District      87877
#> 4     2020 Detroit Public Schools Community District      50016
#> 5     2025 Detroit Public Schools Community District      48117
detroit %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["total"]) +
  geom_point(size = 3, color = colors["total"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma, limits = c(0, NA)) +
  labs(title = "Detroit Public Schools Collapse",
       subtitle = "Lost over 100,000 students since 2000",
       x = "School Year", y = "Students") +
  theme_readme()

2. Statewide enrollment has been declining

Michigan has lost over 100,000 students since 2018 alone, reflecting demographic shifts and economic changes. The state peaked at around 1.7 million K-12 students and now serves approximately 1.4 million.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

state <- enr %>%
  filter(is_state, subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  select(end_year, n_students)
stopifnot(nrow(state) > 0)
state
#>   end_year n_students
#> 1     2018    1468256
#> 2     2019    1453135
#> 3     2020    1444313
#> 4     2021    1398455
#> 5     2022    1392700
#> 6     2023    1383889
#> 7     2024    1373686
#> 8     2025    1366207
print(state)
#>   end_year n_students
#> 1     2018    1468256
#> 2     2019    1453135
#> 3     2020    1444313
#> 4     2021    1398455
#> 5     2022    1392700
#> 6     2023    1383889
#> 7     2024    1373686
#> 8     2025    1366207
state %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["total"]) +
  geom_point(size = 3, color = colors["total"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma) +
  labs(title = "Michigan Statewide Enrollment Decline",
       subtitle = "Total K-12 enrollment trending downward",
       x = "School Year", y = "Students") +
  theme_readme()

3. Grand Rapids is more diverse than you think

Michigan’s second-largest city has become majority-minority, with Hispanic enrollment growing fastest. Grand Rapids Public Schools now has 39.5% Hispanic, 30.1% Black, 22.1% White, and 1.0% Asian students.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

gr <- enr %>%
  filter(is_district, district_name == "Grand Rapids Public Schools",
         grade_level == "TOTAL", end_year == 2025,
         subgroup %in% c("white", "black", "hispanic", "asian")) %>%
  select(subgroup, n_students, pct) %>%
  mutate(pct = round(pct * 100, 1))
stopifnot(nrow(gr) > 0)
gr
#>   subgroup n_students  pct
#> 1    white       2997 22.1
#> 2    black       4077 30.1
#> 3 hispanic       5359 39.5
#> 4    asian        141  1.0
print(gr)
#>   subgroup n_students  pct
#> 1    white       2997 22.1
#> 2    black       4077 30.1
#> 3 hispanic       5359 39.5
#> 4    asian        141  1.0
enr %>%
  filter(is_district, district_name == "Grand Rapids Public Schools",
         grade_level == "TOTAL",
         subgroup %in% c("white", "black", "hispanic", "asian")) %>%
  ggplot(aes(x = end_year, y = pct * 100, color = subgroup)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2.5) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_color_manual(values = colors,
                     labels = c("Asian", "Black", "Hispanic", "White")) +
  labs(title = "Grand Rapids is Majority-Minority",
       subtitle = "More diverse than you might think",
       x = "School Year", y = "Percent", color = "") +
  theme_readme()

4. The Upper Peninsula is emptying out

UP districts have lost over 30% of students since 2000 as the region’s population ages and young families move south. Combined enrollment in Marquette, Houghton, Iron Mountain, and Menominee area districts dropped from 11,280 to 7,733.

library(mischooldata)
library(dplyr)

enr_long <- fetch_enr_multi(c(2000, 2005, 2010, 2020, 2025), use_cache = TRUE)

up_districts <- c("Marquette", "Houghton", "Iron Mountain", "Menominee")
up <- enr_long %>%
  filter(is_district, grepl(paste(up_districts, collapse = "|"), district_name, ignore.case = TRUE),
         subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  group_by(end_year) %>%
  summarize(n_students = sum(n_students, na.rm = TRUE), .groups = "drop")
stopifnot(nrow(up) > 0)
up
#> # A tibble: 5 × 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 1     2000      11280
#> 2     2005      10250
#> 3     2010       9115
#> 4     2020       7975
#> 5     2025       7733
print(up)
#> # A tibble: 5 × 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 1     2000      11280
#> 2     2005      10250
#> 3     2010       9115
#> 4     2020       7975
#> 5     2025       7733
up %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["total"]) +
  geom_point(size = 3, color = colors["total"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma) +
  labs(title = "Upper Peninsula Emptying Out",
       subtitle = "Marquette, Houghton, Iron Mountain, Menominee combined",
       x = "School Year", y = "Students") +
  theme_readme()

5. COVID hit kindergarten hard

Michigan lost nearly 14,000 kindergartners in 2021 (from 120,133 in 2020 to 106,539 in 2021) and hasn’t fully recovered. The pandemic disrupted the transition to formal schooling for thousands of Michigan families.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

k_enr <- enr %>%
  filter(is_state, subgroup == "total_enrollment", grade_level == "K") %>%
  select(end_year, n_students)
stopifnot(nrow(k_enr) > 0)
k_enr
#>   end_year n_students
#> 1     2018     116636
#> 2     2019     117694
#> 3     2020     120133
#> 4     2021     106539
#> 5     2022     114744
#> 6     2023     113864
#> 7     2024     110738
#> 8     2025     108230
print(k_enr)
#>   end_year n_students
#> 1     2018     116636
#> 2     2019     117694
#> 3     2020     120133
#> 4     2021     106539
#> 5     2022     114744
#> 6     2023     113864
#> 7     2024     110738
#> 8     2025     108230
enr %>%
  filter(is_state, subgroup == "total_enrollment",
         grade_level %in% c("K", "01", "06", "12")) %>%
  mutate(grade_label = case_when(
    grade_level == "K" ~ "Kindergarten",
    grade_level == "01" ~ "Grade 1",
    grade_level == "06" ~ "Grade 6",
    grade_level == "12" ~ "Grade 12"
  )) %>%
  ggplot(aes(x = end_year, y = n_students, color = grade_label)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2.5) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma) +
  labs(title = "COVID Hit Michigan Kindergarten Hard",
       subtitle = "Lost nearly 14,000 kindergartners in 2021",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

6. Ann Arbor: island of stability

While Detroit hemorrhages students, Ann Arbor Public Schools maintains around 16,800 students with high diversity. The university town’s economic stability and educated workforce create a different enrollment trajectory.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

aa <- enr %>%
  filter(is_district, grepl("Ann Arbor Public Schools", district_name, ignore.case = TRUE),
         subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  select(end_year, n_students)
stopifnot(nrow(aa) > 0)
aa
#>   end_year n_students
#> 1     2018      17669
#> 2     2019      17950
#> 3     2020      17942
#> 4     2021      17386
#> 5     2022      17016
#> 6     2023      16961
#> 7     2024      16918
#> 8     2025      16810
print(aa)
#>   end_year n_students
#> 1     2018      17669
#> 2     2019      17950
#> 3     2020      17942
#> 4     2021      17386
#> 5     2022      17016
#> 6     2023      16961
#> 7     2024      16918
#> 8     2025      16810
aa %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["total"]) +
  geom_point(size = 3, color = colors["total"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma, limits = c(0, NA)) +
  labs(title = "Ann Arbor: Island of Stability",
       subtitle = "Maintains ~17,000 students while Detroit collapses",
       x = "School Year", y = "Students") +
  theme_readme()

7. Multiracial enrollment growing fastest

Multiracial students are Michigan’s fastest-growing demographic, increasing 31% from 57,291 to 75,055 students since 2018. While overall enrollment declines, multiracial and Hispanic populations continue to grow.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

multiracial_state <- enr %>%
  filter(is_state, subgroup == "multiracial", grade_level == "TOTAL") %>%
  select(end_year, n_students)
stopifnot(nrow(multiracial_state) > 0)
multiracial_state
#>   end_year n_students
#> 1     2018      57291
#> 2     2019      60457
#> 3     2020      63515
#> 4     2021      65101
#> 5     2022      68328
#> 6     2023      70956
#> 7     2024      73294
#> 8     2025      75055
print(multiracial_state)
#>   end_year n_students
#> 1     2018      57291
#> 2     2019      60457
#> 3     2020      63515
#> 4     2021      65101
#> 5     2022      68328
#> 6     2023      70956
#> 7     2024      73294
#> 8     2025      75055
multiracial_state %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["multiracial"]) +
  geom_point(size = 3, color = colors["multiracial"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma) +
  labs(title = "Multiracial Enrollment Growing Statewide",
       subtitle = "Fastest-growing demographic in Michigan schools (+31% since 2018)",
       x = "School Year", y = "Students") +
  theme_readme()

8. Largest districts by enrollment

The 10 largest districts represent a mix of urban, suburban, and diverse communities. Detroit remains the largest despite decades of decline, followed by suburban powerhouses like Utica and Dearborn.

library(mischooldata)
library(dplyr)

enr_current <- fetch_enr(2025, use_cache = TRUE)

largest <- enr_current %>%
  filter(is_district, subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  arrange(desc(n_students)) %>%
  head(10) %>%
  select(district_name, n_students)
stopifnot(nrow(largest) > 0)
largest
#>                                district_name n_students
#> 1  Detroit Public Schools Community District      48117
#> 2                    Utica Community Schools      25092
#> 3              Dearborn City School District      19168
#> 4                   Ann Arbor Public Schools      16810
#> 5          Plymouth-Canton Community Schools      15885
#> 6        Rochester Community School District      14592
#> 7                    Chippewa Valley Schools      14155
#> 8                Grand Rapids Public Schools      13566
#> 9     Livonia Public Schools School District      12818
#> 10               Warren Consolidated Schools      12421
print(largest)
#>                                district_name n_students
#> 1  Detroit Public Schools Community District      48117
#> 2                    Utica Community Schools      25092
#> 3              Dearborn City School District      19168
#> 4                   Ann Arbor Public Schools      16810
#> 5          Plymouth-Canton Community Schools      15885
#> 6        Rochester Community School District      14592
#> 7                    Chippewa Valley Schools      14155
#> 8                Grand Rapids Public Schools      13566
#> 9     Livonia Public Schools School District      12818
#> 10               Warren Consolidated Schools      12421
enr_current %>%
  filter(is_district, subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  arrange(desc(n_students)) %>%
  head(10) %>%
  mutate(district_label = reorder(district_name, n_students)) %>%
  ggplot(aes(x = district_label, y = n_students)) +
  geom_col(fill = colors["total"]) +
  coord_flip() +
  scale_y_continuous(labels = comma) +
  labs(title = "Michigan's Largest School Districts",
       subtitle = "Top 10 districts by total enrollment",
       x = "", y = "Total Students") +
  theme_readme()

9. Flint’s water crisis visible in enrollment

Flint, School District of the City of, has lost 44% of students since 2018, dropping from 4,503 to 2,541. The water crisis accelerated an already declining enrollment as families fled the city.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

flint <- enr %>%
  filter(is_district, grepl("Flint, School District", district_name, ignore.case = TRUE),
         subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  select(end_year, n_students)
stopifnot(nrow(flint) > 0)
flint
#>   end_year n_students
#> 1     2018       4503
#> 2     2019       4183
#> 3     2020       3700
#> 4     2021       3122
#> 5     2022       2989
#> 6     2023       2790
#> 7     2024       2835
#> 8     2025       2541
print(flint)
#>   end_year n_students
#> 1     2018       4503
#> 2     2019       4183
#> 3     2020       3700
#> 4     2021       3122
#> 5     2022       2989
#> 6     2023       2790
#> 7     2024       2835
#> 8     2025       2541
flint %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["total"]) +
  geom_point(size = 3, color = colors["total"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma, limits = c(0, NA)) +
  labs(title = "Flint's Water Crisis Visible in Enrollment",
       subtitle = "Lost over 40% of students during and after the crisis",
       x = "School Year", y = "Students") +
  theme_readme()

10. Oakland County suburbs holding

Oakland County districts like Troy, Rochester, Novi, and Farmington maintain strong enrollment while Detroit collapses. These affluent suburbs benefit from strong economies and excellent school reputations.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

oakland <- c("Troy", "Rochester", "Novi", "Farmington")
oakland_2025 <- enr %>%
  filter(is_district, grepl(paste(oakland, collapse = "|"), district_name, ignore.case = TRUE),
         subgroup == "total_enrollment", grade_level == "TOTAL", end_year == 2025) %>%
  select(district_name, n_students) %>%
  arrange(desc(n_students))
stopifnot(nrow(oakland_2025) > 0)
oakland_2025
#>                         district_name n_students
#> 1 Rochester Community School District      14592
#> 2                Troy School District      12128
#> 3   Farmington Public School District       8937
#> 4      Novi Community School District       6722
print(oakland_2025)
#>                         district_name n_students
#> 1 Rochester Community School District      14592
#> 2                Troy School District      12128
#> 3   Farmington Public School District       8937
#> 4      Novi Community School District       6722
enr %>%
  filter(is_district, grepl(paste(oakland, collapse = "|"), district_name, ignore.case = TRUE),
         subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  ggplot(aes(x = end_year, y = n_students, color = district_name)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2.5) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma) +
  labs(title = "Oakland County Suburbs Holding",
       subtitle = "Troy, Rochester, Novi, Farmington stable",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

11. Dearborn: Arab American educational hub

Dearborn City School District serves one of the largest Arab American communities in the nation with 19,168 students. The district maintains stable enrollment with a unique demographic profile.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

dearborn <- enr %>%
  filter(is_district, grepl("Dearborn City", district_name, ignore.case = TRUE),
         subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  select(end_year, n_students)
stopifnot(nrow(dearborn) > 0)
dearborn
#>   end_year n_students
#> 1     2018      20798
#> 2     2019      20629
#> 3     2020      20535
#> 4     2021      20334
#> 5     2022      20045
#> 6     2023      20013
#> 7     2024      19524
#> 8     2025      19168
print(dearborn)
#>   end_year n_students
#> 1     2018      20798
#> 2     2019      20629
#> 3     2020      20535
#> 4     2021      20334
#> 5     2022      20045
#> 6     2023      20013
#> 7     2024      19524
#> 8     2025      19168
dearborn %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["total"]) +
  geom_point(size = 3, color = colors["total"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma, limits = c(0, NA)) +
  labs(title = "Dearborn: A Unique Michigan Story",
       subtitle = "Home to largest Arab American student population in the US",
       x = "School Year", y = "Students") +
  theme_readme()

12. Black student enrollment declining

Black student enrollment in Michigan has declined from 260,423 in 2018 to 246,009 in 2025, driven primarily by Detroit’s collapse. This demographic shift is reshaping the state’s educational landscape.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

black_state <- enr %>%
  filter(is_state, subgroup == "black", grade_level == "TOTAL") %>%
  select(end_year, n_students)
stopifnot(nrow(black_state) > 0)
black_state
#>   end_year n_students
#> 1     2018     260423
#> 2     2019     256379
#> 3     2020     255296
#> 4     2021     246583
#> 5     2022     246831
#> 6     2023     246629
#> 7     2024     245569
#> 8     2025     246009
print(black_state)
#>   end_year n_students
#> 1     2018     260423
#> 2     2019     256379
#> 3     2020     255296
#> 4     2021     246583
#> 5     2022     246831
#> 6     2023     246629
#> 7     2024     245569
#> 8     2025     246009
black_state %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["black"]) +
  geom_point(size = 3, color = colors["black"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma) +
  labs(title = "Black Student Enrollment Declining",
       subtitle = "Driven by Detroit's population loss",
       x = "School Year", y = "Students") +
  theme_readme()

13. Lansing bucking the urban decline

Unlike Detroit and Flint, Lansing Public School District has maintained relatively stable enrollment around 10,000 students. The state capital’s diverse economy and state government employment provide a buffer.

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

lansing <- enr %>%
  filter(is_district, grepl("Lansing Public School District", district_name, ignore.case = TRUE),
         subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  select(end_year, n_students)
stopifnot(nrow(lansing) > 0)
lansing
#>   end_year n_students
#> 1     2018      10641
#> 2     2019      10462
#> 3     2020      10440
#> 4     2021       9862
#> 5     2022      10015
#> 6     2023       9866
#> 7     2024      10022
#> 8     2025       9808
print(lansing)
#>   end_year n_students
#> 1     2018      10641
#> 2     2019      10462
#> 3     2020      10440
#> 4     2021       9862
#> 5     2022      10015
#> 6     2023       9866
#> 7     2024      10022
#> 8     2025       9808
lansing %>%
  ggplot(aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["total"]) +
  geom_point(size = 3, color = colors["total"]) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma, limits = c(0, NA)) +
  labs(title = "Lansing Bucking the Urban Decline",
       subtitle = "State capital maintains stability while other cities collapse",
       x = "School Year", y = "Students") +
  theme_readme()

14. High school enrollment shrinking faster

High school grades are shrinking faster than elementary grades statewide, as the birth rate decline from the 2008 recession reaches secondary schools. Elementary (K-5) dropped from 652,006 to 615,754 (-5.6%) while high school (9-12) dropped from 477,489 to 440,089 (-7.8%).

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

grade_bands <- enr %>%
  filter(is_state, subgroup == "total_enrollment",
         grade_level %in% c("K", "01", "02", "03", "04", "05",
                            "09", "10", "11", "12")) %>%
  mutate(level = ifelse(grade_level %in% c("K", "01", "02", "03", "04", "05"),
                        "Elementary (K-5)", "High School (9-12)")) %>%
  group_by(end_year, level) %>%
  summarize(n_students = sum(n_students, na.rm = TRUE), .groups = "drop") %>%
  filter(end_year %in% c(2018, 2025))
stopifnot(nrow(grade_bands) > 0)
grade_bands
#> # A tibble: 4 × 3
#>   end_year level              n_students
#>      <dbl> <chr>                   <dbl>
#> 1     2018 Elementary (K-5)       652006
#> 2     2018 High School (9-12)     477489
#> 3     2025 Elementary (K-5)       615754
#> 4     2025 High School (9-12)     440089
print(grade_bands)
#> # A tibble: 4 × 3
#>   end_year level              n_students
#>      <dbl> <chr>                   <dbl>
#> 1     2018 Elementary (K-5)       652006
#> 2     2018 High School (9-12)     477489
#> 3     2025 Elementary (K-5)       615754
#> 4     2025 High School (9-12)     440089
enr %>%
  filter(is_state, subgroup == "total_enrollment",
         grade_level %in% c("K", "01", "02", "03", "04", "05",
                            "09", "10", "11", "12")) %>%
  mutate(level = ifelse(grade_level %in% c("K", "01", "02", "03", "04", "05"),
                        "Elementary (K-5)", "High School (9-12)")) %>%
  group_by(end_year, level) %>%
  summarize(n_students = sum(n_students, na.rm = TRUE), .groups = "drop") %>%
  ggplot(aes(x = end_year, y = n_students, color = level)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2.5) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_y_continuous(labels = comma) +
  labs(title = "High School Shrinking Faster Than Elementary",
       subtitle = "2008 recession birth rate decline reaching high school",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

15. Demographic transformation: Michigan’s changing face

Michigan’s racial demographics are shifting. White students now make up 62.6% (855,383), Black 18.0% (246,009), Hispanic 9.5% (129,236), Multiracial 5.5% (75,055), and Asian 3.8% (51,423).

library(mischooldata)
library(dplyr)

enr <- fetch_enr_multi(2018:2025, use_cache = TRUE)

demo_state <- enr %>%
  filter(is_state, grade_level == "TOTAL", end_year == 2025,
         subgroup %in% c("white", "black", "hispanic", "asian", "multiracial")) %>%
  select(subgroup, n_students, pct) %>%
  mutate(pct = round(pct * 100, 1)) %>%
  arrange(desc(n_students))
stopifnot(nrow(demo_state) > 0)
demo_state
#>      subgroup n_students  pct
#> 1       white     855383 62.6
#> 2       black     246009 18.0
#> 3    hispanic     129236  9.5
#> 4 multiracial      75055  5.5
#> 5       asian      51423  3.8
print(demo_state)
#>      subgroup n_students  pct
#> 1       white     855383 62.6
#> 2       black     246009 18.0
#> 3    hispanic     129236  9.5
#> 4 multiracial      75055  5.5
#> 5       asian      51423  3.8
enr %>%
  filter(is_state, grade_level == "TOTAL",
         subgroup %in% c("white", "black", "hispanic", "asian", "multiracial")) %>%
  ggplot(aes(x = end_year, y = pct * 100, color = subgroup)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2.5) +
  geom_vline(xintercept = 2020.5, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2020.5, y = Inf, label = "COVID", vjust = 2, color = "red", size = 3) +
  scale_color_manual(values = colors,
                     labels = c("Asian", "Black", "Hispanic", "Multiracial", "White")) +
  scale_y_continuous(limits = c(0, NA)) +
  labs(title = "Michigan's Demographic Transformation",
       subtitle = "White enrollment declining, Hispanic and multiracial growing",
       x = "School Year", y = "Percent of Students", color = "") +
  theme_readme()

sessionInfo()
#> R version 4.5.2 (2025-10-31)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.3 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] scales_1.4.0       dplyr_1.2.0        ggplot2_4.0.2      mischooldata_0.2.0
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6       jsonlite_2.0.0     compiler_4.5.2     tidyselect_1.2.1  
#>  [5] jquerylib_0.1.4    systemfonts_1.3.2  textshaping_1.0.5  readxl_1.4.5      
#>  [9] yaml_2.3.12        fastmap_1.2.0      R6_2.6.1           labeling_0.4.3    
#> [13] generics_0.1.4     curl_7.0.0         knitr_1.51         tibble_3.3.1      
#> [17] desc_1.4.3         bslib_0.10.0       pillar_1.11.1      RColorBrewer_1.1-3
#> [21] rlang_1.1.7        utf8_1.2.6         cachem_1.1.0       xfun_0.56         
#> [25] fs_1.6.7           sass_0.4.10        S7_0.2.1           cli_3.6.5         
#> [29] pkgdown_2.2.0      withr_3.0.2        magrittr_2.0.4     digest_0.6.39     
#> [33] grid_4.5.2         rappdirs_0.3.4     lifecycle_1.0.5    vctrs_0.7.1       
#> [37] evaluate_1.0.5     glue_1.8.0         cellranger_1.1.0   farver_2.1.2      
#> [41] codetools_0.2-20   ragg_1.5.1         rmarkdown_2.30     purrr_1.2.1       
#> [45] httr_1.4.8         tools_4.5.2        pkgconfig_2.0.3    htmltools_0.5.9