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")
# Get available years
years <- get_available_years()
max_year <- max(years)
min_year <- min(years)
# Fetch recent data (2016-2024; 2015 has no cached data)
enr <- tryCatch({
df <- fetch_enr_multi(2016:max_year, use_cache = TRUE)
# Filter out any years with zero state total (missing data)
good_years <- df %>%
filter(is_state, subgroup == "total_enrollment", grade_level == "TOTAL",
n_students > 0) %>%
pull(end_year)
df %>% filter(end_year %in% good_years)
}, error = function(e) {
warning("Could not fetch enrollment data: ", e$message)
NULL
})
# Key years for long-term trend
key_years <- get_available_years()
enr_long <- tryCatch({
df <- fetch_enr_multi(key_years, use_cache = TRUE)
good_years <- df %>%
filter(is_state, subgroup == "total_enrollment", grade_level == "TOTAL",
n_students > 0) %>%
pull(end_year)
df %>% filter(end_year %in% good_years)
}, error = function(e) {
warning("Could not fetch long-term data: ", e$message)
NULL
})
enr_current <- tryCatch({
fetch_enr(max_year, use_cache = TRUE)
}, error = function(e) {
warning("Could not fetch current year data: ", e$message)
NULL
})
# Check if we have data
has_data <- !is.null(enr) && nrow(enr) > 01. Mississippi is majority Black in many districts
Unlike most Southern states, Mississippi has numerous majority-Black school districts, especially in the Delta region.
black <- enr_current %>%
filter(is_district, subgroup == "black", grade_level == "TOTAL") %>%
arrange(desc(pct)) %>%
head(10) %>%
mutate(district_label = reorder(district_name, pct))
stopifnot(nrow(black) > 0)
black %>% select(district_name, pct)
ggplot(black, aes(x = district_label, y = pct)) +
geom_col(fill = colors["black"]) +
coord_flip() +
labs(title = "Mississippi Has Many Majority-Black Districts",
subtitle = "Especially in the Delta region",
x = "", y = "Percent Black Students") +
theme_readme()2. The Delta is emptying out
Districts in the Mississippi Delta have seen steep enrollment declines since 2007.
delta <- c("Coahoma County", "Sunflower County", "Leflore County")
delta_trend <- enr_long %>%
filter(is_district, grepl(paste(delta, 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(delta_trend) > 0)
delta_trend
ggplot(delta_trend, aes(x = end_year, y = n_students)) +
geom_line(linewidth = 1.5, color = colors["total"]) +
geom_point(size = 3, color = colors["total"]) +
scale_y_continuous(labels = comma) +
labs(title = "The Delta is Emptying Out",
subtitle = "Coahoma, Sunflower, Leflore counties combined",
x = "School Year", y = "Students") +
theme_readme()3. DeSoto County: Mississippi’s largest district
Bordering Memphis, DeSoto County grew 21% since 2007 to become Mississippi’s largest district with nearly 35,000 students.
desoto <- enr %>%
filter(is_district, grepl("DeSoto|Desoto", district_name, ignore.case = TRUE),
subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(desoto) > 0)
desoto %>% select(end_year, district_name, n_students)
ggplot(desoto, aes(x = end_year, y = n_students)) +
geom_line(linewidth = 1.5, color = colors["total"]) +
geom_point(size = 3, color = colors["total"]) +
scale_y_continuous(labels = comma, limits = c(0, NA)) +
labs(title = "DeSoto County: Mississippi's Largest District",
subtitle = "Memphis suburb grew 21% since 2007",
x = "School Year", y = "Students") +
theme_readme()4. Jackson Public Schools’ steep decline
Mississippi’s capital city has lost 44% of students since 2007, from 32,000 to under 18,000.
jackson <- enr %>%
filter(is_district, grepl("Jackson Public", district_name, ignore.case = TRUE),
subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(jackson) > 0)
jackson %>% select(end_year, district_name, n_students)
ggplot(jackson, aes(x = end_year, y = n_students)) +
geom_line(linewidth = 1.5, color = colors["total"]) +
geom_point(size = 3, color = colors["total"]) +
scale_y_continuous(labels = comma, limits = c(0, NA)) +
labs(title = "Jackson Public Schools' Steep Decline",
subtitle = "Capital city lost 44% of students since 2007",
x = "School Year", y = "Students") +
theme_readme()5. Mississippi lost 50,000 students in a decade
State enrollment dropped from 487,000 in 2016 to 437,000 in 2024, a 10% decline.
state_trend <- enr %>%
filter(is_state, subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(state_trend) > 0)
state_trend %>% select(end_year, n_students)
ggplot(state_trend, aes(x = end_year, y = n_students)) +
geom_line(linewidth = 1.5, color = colors["total"]) +
geom_point(size = 3, color = colors["total"]) +
scale_y_continuous(labels = comma, limits = c(0, NA)) +
labs(title = "Mississippi Enrollment Declined 10% Since 2016",
subtitle = "State lost ~50,000 students from 487K to 437K",
x = "School Year", y = "Students") +
theme_readme()6. COVID hit kindergarten hard
Mississippi lost 13% of kindergartners in 2021 and enrollment hasn’t recovered.
k_trend <- 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"
))
stopifnot(nrow(k_trend) > 0)
k_trend %>%
filter(grade_level == "K") %>%
select(end_year, n_students)
ggplot(k_trend, aes(x = end_year, y = n_students, color = grade_label)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2.5) +
geom_vline(xintercept = 2021, linetype = "dashed", color = "red", alpha = 0.5) +
scale_y_continuous(labels = comma) +
labs(title = "COVID Hit Mississippi Kindergarten Hard",
subtitle = "Lost 13% of kindergartners and hasn't recovered",
x = "School Year", y = "Students", color = "") +
theme_readme()7. Madison County holds steady while Jackson shrinks
Madison County has maintained enrollment around 13,000 while neighboring Jackson declined – a sign of suburban stability.
madison <- enr %>%
filter(is_district, grepl("Madison County", district_name, ignore.case = TRUE),
subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(madison) > 0)
madison %>% select(end_year, district_name, n_students)
ggplot(madison, aes(x = end_year, y = n_students)) +
geom_line(linewidth = 1.5, color = colors["total"]) +
geom_point(size = 3, color = colors["total"]) +
scale_y_continuous(labels = comma, limits = c(0, NA)) +
labs(title = "Madison County Holds Steady",
subtitle = "Suburban district maintained enrollment while Jackson declined",
x = "School Year", y = "Students") +
theme_readme()8. Hispanic students reach 46% in Forest Municipal
Forest Municipal School District is 46% Hispanic, with several other districts above 15%.
hispanic <- enr_current %>%
filter(is_district, subgroup == "hispanic", grade_level == "TOTAL") %>%
arrange(desc(pct)) %>%
head(10) %>%
mutate(district_label = reorder(district_name, pct))
stopifnot(nrow(hispanic) > 0)
hispanic %>% select(district_name, pct)
ggplot(hispanic, aes(x = district_label, y = pct)) +
geom_col(fill = colors["hispanic"]) +
coord_flip() +
labs(title = "Hispanic Population Growing",
subtitle = "Forest Municipal at 46%, several districts above 15%",
x = "", y = "Percent Hispanic Students") +
theme_readme()9. The Coast is holding steady
Gulf Coast districts have maintained enrollment despite hurricanes.
coast <- c("Harrison County", "Jackson County", "Hancock County")
coast_trend <- enr %>%
filter(is_district, grepl(paste(coast, collapse = "|"), district_name, ignore.case = TRUE),
subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(coast_trend) > 0)
coast_trend %>%
group_by(end_year) %>%
summarize(total = sum(n_students, na.rm = TRUE), .groups = "drop")
ggplot(coast_trend, aes(x = end_year, y = n_students, color = district_name)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2.5) +
scale_y_continuous(labels = comma) +
labs(title = "The Coast is Holding Steady",
subtitle = "Gulf Coast districts maintained enrollment despite hurricanes",
x = "School Year", y = "Students", color = "") +
theme_readme()10. Charter schools are minimal
Mississippi has one of the smallest charter sectors in the nation, with fewer than 5,000 students enrolled across all charter schools.
Note: Charter school enrollment tracking is not yet implemented in this package. The MDE data portal does not currently distinguish charter schools as a separate entity type.
11. Mississippi is nearly 47% Black statewide
Mississippi has the highest percentage of Black students of any US state, with Black students outnumbering white students statewide.
race <- enr_current %>%
filter(is_state, grade_level == "TOTAL",
subgroup %in% c("white", "black", "hispanic", "asian")) %>%
mutate(subgroup_label = case_when(
subgroup == "white" ~ "White",
subgroup == "black" ~ "Black",
subgroup == "hispanic" ~ "Hispanic",
subgroup == "asian" ~ "Asian"
))
stopifnot(nrow(race) > 0)
race %>% select(subgroup_label, n_students, pct)
ggplot(race, aes(x = reorder(subgroup_label, -pct), y = pct, fill = subgroup)) +
geom_col() +
scale_fill_manual(values = colors, guide = "none") +
labs(title = "Mississippi's Racial Demographics",
subtitle = "Nearly 47% Black students - highest in the nation",
x = "", y = "Percent of Students") +
theme_readme()12. DeSoto and Rankin dominate enrollment rankings
The top 15 districts account for nearly half of all Mississippi students, with Memphis and Jackson suburbs leading the pack.
top_districts <- enr_current %>%
filter(is_district, subgroup == "total_enrollment", grade_level == "TOTAL") %>%
arrange(desc(n_students)) %>%
head(15) %>%
mutate(district_label = reorder(district_name, n_students))
stopifnot(nrow(top_districts) > 0)
top_districts %>% select(district_name, n_students)
ggplot(top_districts, aes(x = district_label, y = n_students)) +
geom_col(fill = colors["total"]) +
coord_flip() +
scale_y_continuous(labels = comma) +
labs(title = "Top 15 Largest Districts",
subtitle = "DeSoto, Rankin, and Jackson lead the state",
x = "", y = "Students") +
theme_readme()13. Rankin County is the stable suburban anchor
Rankin County (east of Jackson) has maintained enrollment around 19,000 while the capital city declined.
rankin <- enr %>%
filter(is_district, grepl("Rankin County", district_name, ignore.case = TRUE),
subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(rankin) > 0)
rankin %>% select(end_year, district_name, n_students)
ggplot(rankin, aes(x = end_year, y = n_students)) +
geom_line(linewidth = 1.5, color = colors["total"]) +
geom_point(size = 3, color = colors["total"]) +
scale_y_continuous(labels = comma, limits = c(0, NA)) +
labs(title = "Rankin County: Stable Suburban Anchor",
subtitle = "Maintained enrollment while Jackson declined",
x = "School Year", y = "Students") +
theme_readme()14. Mississippi’s gender balance is nearly even
Like most states, Mississippi schools are roughly 51% male and 49% female, with slight variation by district.
gender <- enr %>%
filter(is_state, grade_level == "TOTAL",
subgroup %in% c("male", "female")) %>%
mutate(subgroup_label = ifelse(subgroup == "male", "Male", "Female"))
stopifnot(nrow(gender) > 0)
gender %>%
filter(end_year == max_year) %>%
select(subgroup_label, n_students, pct)
ggplot(gender, aes(x = end_year, y = pct, color = subgroup_label)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2.5) +
labs(title = "Gender Balance Over Time",
subtitle = "Slight male majority, consistent over years",
x = "School Year", y = "Percent of Students", color = "") +
theme_readme()Session Info
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 msschooldata_0.1.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 yaml_2.3.12
#> [9] fastmap_1.2.0 R6_2.6.1 generics_0.1.4 curl_7.0.0
#> [13] knitr_1.51 tibble_3.3.1 desc_1.4.3 bslib_0.10.0
#> [17] pillar_1.11.1 RColorBrewer_1.1-3 rlang_1.1.7 cachem_1.1.0
#> [21] xfun_0.56 fs_1.6.7 sass_0.4.10 S7_0.2.1
#> [25] cli_3.6.5 pkgdown_2.2.0 withr_3.0.2 magrittr_2.0.4
#> [29] digest_0.6.39 grid_4.5.2 rappdirs_0.3.4 lifecycle_1.0.5
#> [33] vctrs_0.7.1 evaluate_1.0.5 glue_1.8.0 farver_2.1.2
#> [37] codetools_0.2-20 ragg_1.5.1 rmarkdown_2.30 purrr_1.2.1
#> [41] httr_1.4.8 tools_4.5.2 pkgconfig_2.0.3 htmltools_0.5.9