Skip to contents

Fetch and analyze Michigan school enrollment data from the Center for Educational Performance and Information (CEPI) in R or Python.

Part of the njschooldata family.

Full documentation — all 15 stories with interactive charts, getting-started guide, and complete function reference.

Highlights

library(mischooldata)
library(ggplot2)
library(dplyr)
library(scales)

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
#> # A tibble: 5 x 3
#>   end_year district_name                            n_students
#>      <dbl> <chr>                                         <dbl>
#> 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 decline
Detroit decline

(source)


2. 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
#> # A tibble: 8 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 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 crisis
Flint crisis

(source)


3. 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
#> # A tibble: 8 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 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 growth
Multiracial growth

(source)


Data Taxonomy

Category Years Function Details
Enrollment 1996-2025 fetch_enr() / fetch_enr_multi() State, district, building. Race, gender
Assessments 2007-2025 fetch_assessment() / fetch_assessment_multi() M-STEP (2015+), MEAP (2007-2014). ELA, Math, Science, Social Studies. No 2020 (COVID)
Graduation Not yet available
Directory Current fetch_directory() ISD, LEA, PSA districts and schools. Addresses, phone, superintendent/principal, grades served
Per-Pupil Spending Not yet available
Accountability Not yet available
Chronic Absence Not yet available
EL Progress Not yet available
Special Ed Not yet available

See DATA-CATEGORY-TAXONOMY.md for what each category covers.

Quick Start

R

# install.packages("remotes")
remotes::install_github("almartin82/mischooldata")
library(mischooldata)
library(dplyr)

# Fetch one year
enr_2025 <- fetch_enr(2025, use_cache = TRUE)

# Fetch multiple years
enr_multi <- fetch_enr_multi(2020:2025, use_cache = TRUE)

# State totals
enr_2025 %>%
  filter(is_state, subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  select(end_year, n_students)
#> # A tibble: 1 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 1     2025    1366207

# Largest districts
enr_2025 %>%
  filter(is_district, subgroup == "total_enrollment", grade_level == "TOTAL") %>%
  arrange(desc(n_students)) %>%
  head(5) %>%
  select(district_name, n_students)
#> # A tibble: 5 x 2
#>   district_name                            n_students
#>   <chr>                                         <dbl>
#> 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

Python

import pymischooldata as mi

# Check available years
years = mi.get_available_years()
print(f"Data available from {years['min_year']} to {years['max_year']}")
#> Data available from 1996 to 2025

# Fetch one year
enr_2025 = mi.fetch_enr(2025)

# State totals
state_total = enr_2025[
    (enr_2025['is_state'] == True) &
    (enr_2025['subgroup'] == 'total_enrollment') &
    (enr_2025['grade_level'] == 'TOTAL')
]
print(state_total[['end_year', 'n_students']])
#>    end_year  n_students
#> 0      2025     1366207

Explore More

Data Notes

Data Source

All data is sourced directly from the Michigan Center for Educational Performance and Information (CEPI): - Main portal: https://www.mischooldata.org/ - Data files: https://www.mischooldata.org/student-enrollment-counts-data-files/ - CEPI home: https://www.michigan.gov/cepi

Available Years

  • Full range: 1996-2025 (30 school years)
  • Note: 2015 data uses .xlsb format which requires special handling

Collection Period

Michigan enrollment data is collected on Count Day, typically the first Wednesday in October (Fall Count) and the second Wednesday in February (Spring Count). This package uses Fall Count data.

Suppression Rules

CEPI suppresses (shows as blank/NA) student counts when: - Cell counts are less than 10 students for demographic breakdowns - The suppression is necessary to prevent identification of individual students

Known Data Quality Issues

  1. Detroit restructuring (2016): Detroit City School District (82010) was restructured as Detroit Public Schools Community District (82015) in 2016. Historical analysis should account for this ID change.

  2. 2015 XLSB format gap: The 2014-15 school year data file uses .xlsb (Excel Binary) format instead of .xlsx. The readxl package cannot read .xlsb files natively. Multi-year fetches that include 2015 (e.g., fetch_enr_multi(2014:2016)) will skip this year. To include 2015 data, convert the file to .xlsx manually or use a dedicated .xlsb reader.

  3. Charter school proliferation: Michigan has ~300 charter schools (PSAs - Public School Academies) which are counted as separate districts.

  4. Schools of Choice: Inter-district enrollment means some students are counted in districts different from their residence.

Entity Levels

  • State: Statewide totals
  • District: 880+ traditional public school districts plus charter schools
  • Building: 3,500+ individual schools

District/Building Codes

  • District Code: 5 digits (e.g., 82015 = Detroit Public Schools)
  • Building Code: 5 digits
  • ISD Code: First 2 digits of district code indicate Intermediate School District

Key ISDs (Intermediate School Districts)

Code ISD Name Major Districts
82 Wayne RESA Detroit, Dearborn
63 Oakland Schools Troy, Rochester, Novi
50 Macomb ISD Utica, Warren
33 Kent ISD Grand Rapids

Deeper Dive


4. 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
#> # A tibble: 8 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 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 decline
State decline

(source)


5. 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
#> # A tibble: 4 x 3
#>   subgroup n_students   pct
#>   <chr>         <dbl> <dbl>
#> 1 white          2997  22.1
#> 2 black          4077  30.1
#> 3 hispanic       5359  39.5
#> 4 asian           141   1.0
Grand Rapids diversity
Grand Rapids diversity

(source)


6. 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 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 1     2000      11280
#> 2     2005      10250
#> 3     2010       9115
#> 4     2020       7975
#> 5     2025       7733
UP decline
UP decline

(source)


7. 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
#> # A tibble: 8 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 1     2018     116636
#> 2     2019     117694
#> 3     2020     120133
#> 4     2021     106539
#> 5     2022     114744
#> 6     2023     113864
#> 7     2024     110738
#> 8     2025     108230
COVID kindergarten
COVID kindergarten

(source)


8. 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
#> # A tibble: 8 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 1     2018      17669
#> 2     2019      17950
#> 3     2020      17942
#> 4     2021      17386
#> 5     2022      17016
#> 6     2023      16961
#> 7     2024      16918
#> 8     2025      16810
Ann Arbor stable
Ann Arbor stable

(source)


9. 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
#> # A tibble: 10 x 2
#>    district_name                            n_students
#>    <chr>                                         <dbl>
#>  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
Largest districts
Largest districts

(source)


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
#> # A tibble: 4 x 2
#>   district_name                    n_students
#>   <chr>                                 <dbl>
#> 1 Rochester Community School District   14592
#> 2 Troy School District                  12128
#> 3 Farmington Public School District      8937
#> 4 Novi Community School District         6722
Oakland suburbs
Oakland suburbs

(source)


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
#> # A tibble: 8 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 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 enrollment
Dearborn enrollment

(source)


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
#> # A tibble: 8 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 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 decline
Black decline

(source)


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
#> # A tibble: 8 x 2
#>   end_year n_students
#>      <dbl>      <dbl>
#> 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 stable
Lansing stable

(source)


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 x 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
High school vs elementary
High school vs elementary

(source)


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
#> # A tibble: 5 x 3
#>   subgroup    n_students   pct
#>   <chr>            <dbl> <dbl>
#> 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
Demographic shift
Demographic shift

(source)