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