1.4 million students. 580+ districts. 21 counties. 25+ years of data from the New Jersey Department of Education, in one clean R and Python interface.
Full documentation – all 15 stories with interactive charts, getting-started guide, and complete function reference.
Highlights
1. Charter enrollment grew 15% in five years
New Jersey’s charter sector added 8,000+ students from 2020 to 2025, growing from 55,600 to over 63,800.
library(njschooldata)
library(ggplot2)
library(dplyr)
library(scales)
years <- 2020:2025
enr_all <- purrr::map_df(years, ~{
tryCatch(
fetch_enr(.x, tidy = TRUE),
error = function(e) {
warning(paste("Year", .x, "failed:", conditionMessage(e)))
NULL
}
)
})
charter_trend <- enr_all %>%
filter(is_district, subgroup == "total_enrollment", grade_level == "TOTAL") %>%
mutate(sector = ifelse(is_charter, "Charter", "Traditional")) %>%
group_by(end_year, sector) %>%
summarize(n_students = sum(n_students, na.rm = TRUE), .groups = "drop")
stopifnot(nrow(charter_trend) > 0)
charter_trend
#> # A tibble: 12 x 3
#> end_year sector n_students
#> <dbl> <chr> <dbl>
#> 1 2020 Charter 55604.
#> 2 2020 Traditional 1320225.
#> 3 2021 Charter 57480
#> 4 2021 Traditional 1304920
#> 5 2022 Charter 58776.
#> 6 2022 Traditional 1302140.
#> 7 2023 Charter 58568.
#> 8 2023 Traditional 1313352.
#> 9 2024 Charter 61295
#> 10 2024 Traditional 1318693
#> 11 2025 Charter 63810.
#> 12 2025 Traditional 1317372.
2. White students dropped below 37%
White students went from 42% to under 37% of NJ public school enrollment in just five years. NJ public schools are now decisively majority-minority.
# State-level summary aggregated from district totals for time-series consistency
state_summary <- enr_all %>%
filter(is_district) %>%
group_by(end_year, subgroup, grade_level) %>%
summarize(n_students = sum(n_students, na.rm = TRUE), .groups = "drop")
state_totals <- state_summary %>%
filter(subgroup == "total_enrollment") %>%
select(end_year, grade_level, total = n_students)
state_summary <- state_summary %>%
left_join(state_totals, by = c("end_year", "grade_level")) %>%
mutate(pct = n_students / total)
demo <- state_summary %>%
filter(subgroup %in% c("white", "hispanic", "black", "asian"),
grade_level == "TOTAL") %>%
mutate(subgroup = factor(subgroup, levels = c("white", "hispanic", "black", "asian")))
stopifnot(nrow(demo) > 0)
demo %>% select(end_year, subgroup, pct) %>%
mutate(pct = round(pct * 100, 1)) %>%
tidyr::pivot_wider(names_from = subgroup, values_from = pct)
#> # A tibble: 6 x 5
#> end_year white hispanic black asian
#> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2020 42 30.3 14.6 10.3
#> 2 2021 40.6 31.1 14.9 10.4
#> 3 2022 39.6 32.1 14.8 10.3
#> 4 2023 38.5 33.2 14.6 10.3
#> 5 2024 37.6 34.1 14.4 10.3
#> 6 2025 36.7 35.0 14.3 10.3
3. Kindergarten rebounded from COVID
New Jersey lost 9% of kindergartners during COVID - but by 2025, K enrollment nearly recovered while Pre-K surged past pre-pandemic levels.
k_trend <- state_summary %>%
filter(subgroup == "total_enrollment",
grade_level %in% c("PK", "K", "01", "06", "12")) %>%
mutate(grade_label = case_when(
grade_level == "PK" ~ "Pre-K",
grade_level == "K" ~ "Kindergarten",
grade_level == "01" ~ "Grade 1",
grade_level == "06" ~ "Grade 6",
grade_level == "12" ~ "Grade 12",
TRUE ~ grade_level
))
stopifnot(nrow(k_trend) > 0)
k_trend %>%
filter(grade_level %in% c("K", "PK")) %>%
select(end_year, grade_label, n_students)
#> # A tibble: 12 x 3
#> end_year grade_label n_students
#> <dbl> <chr> <dbl>
#> 1 2020 Kindergarten 90818
#> 2 2020 Pre-K 45013
#> 3 2021 Kindergarten 82604
#> 4 2021 Pre-K 56396
#> 5 2022 Kindergarten 86202
#> 6 2022 Pre-K 65350
#> 7 2023 Kindergarten 85873
#> 8 2023 Pre-K 71615
#> 9 2024 Kindergarten 90783
#> 10 2024 Pre-K 83463
#> 11 2025 Kindergarten 89428
#> 12 2025 Pre-K 87231
Data Taxonomy
| Category | Years | Function | Details |
|---|---|---|---|
| Enrollment | 2000-2025 | fetch_enr() |
State, county, district, school. Race, gender, FRPL, LEP, migrant |
| Assessments | 2004-2024 |
fetch_parcc() / fetch_njask() / fetch_njgpa()
|
NJSLA, PARCC, NJASK, HSPA, GEPA. ELA, Math, Science |
| Graduation | 2011-2024 |
fetch_grad_rate() / fetch_grad_count()
|
4-yr and 6-yr ACGR. District and school level |
| Directory | Current |
get_school_directory() / get_district_directory()
|
Names, IDs, addresses, school type |
| Per-Pupil Spending | – | – | Not yet available |
| Accountability | 2018+ |
fetch_essa_status() / fetch_essa_progress()
|
CSI/TSI lists, ESSA indicators |
| Chronic Absence | 2017-2024 |
fetch_absence() / fetch_chronic_absenteeism() / fetch_days_absent()
|
By grade, by demographic. Cross-state standard via fetch_absence()
|
| EL Progress | 2022-2024 | fetch_access() |
WIDA ACCESS for ELLs |
| Special Ed | 2024+ | fetch_sped() |
Classification rates by disability category |
| Discipline | Available |
fetch_disciplinary_removals() / fetch_violence_vandalism_hib()
|
Suspensions, expulsions, HIB incidents |
| Staff | Available |
fetch_staff_demographics() / fetch_teacher_experience()
|
Demographics, experience, ratios |
| College-Going | Available |
fetch_postsecondary() / fetch_sat_participation() / fetch_ap_participation()
|
Postsecondary enrollment, SAT, AP |
| Courses | Available |
fetch_math_course_enrollment() / fetch_cs_enrollment()
|
Math, science, CS, arts, world languages |
| CTE | Available |
fetch_cte_participation() / fetch_industry_credentials()
|
Career pathways, credentials, apprenticeships |
See the full data category taxonomy
Quick Start
R
# Install from GitHub
remotes::install_github("almartin82/njschooldata")
library(njschooldata)
# Enrollment data
enr_2025 <- fetch_enr(2025, tidy = TRUE)
# Assessment data
math_g4 <- fetch_parcc(2024, grade_or_subj = 4, subj = 'math')
# Graduation rates
grate <- fetch_grad_rate(2024)
# School directory
schools <- get_school_directory()Explore More
Full analysis with 15 stories:
- NJ Enrollment Insights – 15 stories covering charter growth, demographics, COVID recovery, poverty gaps, and more
- Getting Started – installation, data fetching, and tidy format guide
- Function reference
Data Notes
Source: New Jersey Department of Education – all data comes directly from NJ DOE, not federal sources.
Available years: Enrollment data from 2000-2025. Tidy format (2020+) provides consistent structure with district, charter, and school-level records. Assessments from 2004-2024 span four different testing systems (GEPA, NJASK, PARCC, NJSLA).
Suppression rules: NJ DOE suppresses counts below 10 in some data types. Enrollment data uses half-day weighting for programs like pre-K, which can produce non-integer counts.
Census Day: NJ enrollment counts are based on October 15 enrollment (ASSA reporting).
Known caveats: - 2020+ enrollment data includes state-level rows but the vignette aggregates from district-level for time-series consistency - Charter schools appear as separate “districts” in the data - Pre-2020 and post-2020 data formats differ significantly
Deeper Dive
4. New Jersey educates 1.4 million students
New Jersey has one of the largest public school systems in the country, with enrollment holding steady through COVID and beyond.
state_total <- state_summary %>%
filter(subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(state_total) > 0)
state_total
#> # A tibble: 6 x 6
#> end_year subgroup grade_level n_students total pct
#> <dbl> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 2020 total_enrollment TOTAL 1375828. 1375828. 1
#> 2 2021 total_enrollment TOTAL 1362400 1362400 1
#> 3 2022 total_enrollment TOTAL 1360916 1360916 1
#> 4 2023 total_enrollment TOTAL 1371921 1371921 1
#> 5 2024 total_enrollment TOTAL 1379988 1379988 1
#> 6 2025 total_enrollment TOTAL 1381182 1381182 1
5. Hispanic students hit 35% and rising
Hispanic enrollment surged from 30% to 35% of all NJ students in just five years, making it the fastest demographic shift in state history.
hispanic <- state_summary %>%
filter(subgroup == "hispanic", grade_level == "TOTAL")
stopifnot(nrow(hispanic) > 0)
hispanic %>% select(end_year, n_students, pct)
#> # A tibble: 6 x 3
#> end_year n_students pct
#> <dbl> <dbl> <dbl>
#> 1 2020 417042. 0.303
#> 2 2021 424170. 0.311
#> 3 2022 437187 0.321
#> 4 2023 455576. 0.332
#> 5 2024 470906 0.341
#> 6 2025 483504. 0.350
6. The Big Three: Newark, Jersey City, and Paterson
New Jersey’s three largest districts educate over 93,000 students combined - nearly 7% of the state.
big_three_names <- c("Newark Public School District",
"Jersey City Public Schools",
"Paterson Public School District")
big_three_trend <- enr_all %>%
filter(is_district, !is_charter,
district_name %in% big_three_names,
subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(big_three_trend) > 0)
big_three_trend %>% select(end_year, district_name, n_students)
#> # A tibble: 15 x 3
#> end_year district_name n_students
#> <dbl> <chr> <dbl>
#> 1 2021 Newark Public School District 40085
#> 2 2021 Jersey City Public Schools 26541
#> 3 2021 Paterson Public School District 25657
#> 4 2022 Newark Public School District 40607
#> 5 2022 Jersey City Public Schools 26890
#> 6 2022 Paterson Public School District 24495
#> 7 2023 Newark Public School District 41430
#> 8 2023 Jersey City Public Schools 26418
#> 9 2023 Paterson Public School District 26067
#> 10 2024 Newark Public School District 42600
#> 11 2024 Jersey City Public Schools 26023
#> 12 2024 Paterson Public School District 24090
#> 13 2025 Newark Public School District 43980
#> 14 2025 Jersey City Public Schools 25692
#> 15 2025 Paterson Public School District 23609
7. Free/reduced lunch ranges from 98% to under 5%
Passaic City has 98% of students on free/reduced lunch while affluent suburbs like Millburn have under 5% - a stark measure of NJ’s wealth divide.
enr_current <- fetch_enr(2025, tidy = TRUE)
frl <- enr_current %>%
filter(is_district, !is_charter,
subgroup == "free_reduced_lunch", grade_level == "TOTAL",
!is.na(pct), n_students >= 100) %>%
arrange(desc(pct)) %>%
head(15) %>%
mutate(district_label = reorder(district_name, pct))
stopifnot(nrow(frl) > 0)
frl %>% select(district_name, n_students, pct)
#> # A tibble: 15 x 3
#> district_name n_students pct
#> <chr> <dbl> <dbl>
#> 1 Passaic City School District 11540. 0.981
#> 2 Kipp: Cooper Norcross, A New Jersey Nonprofit Corporation 2216. 0.972
#> 3 Mastery Schools Of Camden, Inc. 2735. 0.952
#> 4 Camden Prep, Inc. 1402. 0.936
#> 5 Bridgeton City School District 5579. 0.923
#> 6 Lakewood Township School District 3783. 0.920
#> 7 Atlantic City School District 5577. 0.870
#> 8 New Brunswick School District 7584. 0.867
#> 9 West New York School District 6683. 0.848
#> 10 Lindenwold Public School District 2634. 0.842
#> 11 Guttenberg School District 809. 0.828
#> 12 Harrison Public Schools 2016. 0.826
#> 13 Elizabeth Public Schools 22915. 0.819
#> 14 Bound Brook School District 1555. 0.813
#> 15 Woodbine School District 200. 0.810
8. English learners top 45% in some districts
ELL students make up over 45% in Plainfield and Lakewood but under 1% in most suburban districts - a concentration driven by immigration patterns.
ell <- enr_current %>%
filter(is_district, !is_charter,
subgroup == "lep", grade_level == "TOTAL",
!is.na(pct), n_students >= 50) %>%
arrange(desc(pct)) %>%
head(15) %>%
mutate(district_label = reorder(district_name, pct))
stopifnot(nrow(ell) > 0)
ell %>% select(district_name, n_students, pct)
#> # A tibble: 15 x 3
#> district_name n_students pct
#> <chr> <dbl> <dbl>
#> 1 Plainfield Public School District 4486. 0.452
#> 2 Lakewood Township School District 1842. 0.448
#> 3 Dover Public School District 1462. 0.427
#> 4 New Brunswick School District 3656. 0.418
#> 5 Red Bank Borough Public School District 492. 0.417
#> 6 Trenton Public School District 6437. 0.416
#> 7 Irvington Public School District 3255. 0.403
#> 8 Union City School District 4971. 0.394
#> 9 Bridgeton City School District 2381. 0.394
#> 10 Elizabeth Public Schools 10688. 0.382
#> 11 East Newark School District 82. 0.369
#> 12 Perth Amboy Public School District 3703. 0.367
#> 13 Passaic City School District 4294. 0.365
#> 14 Paterson Public School District 8169. 0.346
#> 15 Bound Brook School District 645. 0.337
9. Top 10 districts serve 15% of all students
Just 10 out of 580+ districts educate nearly 1 in 6 NJ students. Newark alone has 44,000.
top_10 <- enr_current %>%
filter(is_district, !is_charter,
subgroup == "total_enrollment", grade_level == "TOTAL") %>%
arrange(desc(n_students)) %>%
head(10) %>%
mutate(district_label = reorder(district_name, n_students))
stopifnot(nrow(top_10) > 0)
top_10 %>% select(district_name, n_students)
#> # A tibble: 10 x 2
#> district_name n_students
#> <chr> <dbl>
#> 1 Newark Public School District 43980
#> 2 Elizabeth Public Schools 27980.
#> 3 Jersey City Public Schools 25692
#> 4 Paterson Public School District 23609
#> 5 Edison Township School District 16708
#> 6 Trenton Public School District 15474.
#> 7 Toms River Regional School District 14118.
#> 8 Woodbridge Township School District 13870.
#> 9 Union City School District 12617
#> 10 Hamilton Township Public School District 12194.
10. Multiracial students: fastest-growing category
Multiracial students grew 39% in five years - from 2.4% to 3.3% of enrollment - making it the fastest-growing racial category in NJ.
multi <- state_summary %>%
filter(subgroup == "multiracial", grade_level == "TOTAL")
stopifnot(nrow(multi) > 0)
multi %>% select(end_year, n_students, pct)
#> # A tibble: 6 x 3
#> end_year n_students pct
#> <dbl> <dbl> <dbl>
#> 1 2020 32622 0.0237
#> 2 2021 34518 0.0253
#> 3 2022 37474 0.0275
#> 4 2023 40934. 0.0298
#> 5 2024 43436. 0.0315
#> 6 2025 45246. 0.0327
11. Pre-K nearly doubled since 2020
NJ’s Pre-K enrollment surged from 45,000 to 87,000 in five years - fueled by the state’s expanding universal pre-K program.
prek <- state_summary %>%
filter(subgroup == "total_enrollment", grade_level == "PK")
stopifnot(nrow(prek) > 0)
prek %>% select(end_year, n_students)
#> # A tibble: 6 x 2
#> end_year n_students
#> <dbl> <dbl>
#> 1 2020 45013
#> 2 2021 56396
#> 3 2022 65350
#> 4 2023 71615
#> 5 2024 83463
#> 6 2025 87231
12. Bergen County has more students than several US states
With 132,000+ students, Bergen County alone has a larger public school system than Wyoming, Vermont, North Dakota, and other small states.
county_enr <- enr_current %>%
filter(is_district, subgroup == "total_enrollment", grade_level == "TOTAL") %>%
group_by(county_name) %>%
summarize(n_students = sum(n_students, na.rm = TRUE),
n_districts = n(), .groups = "drop") %>%
filter(county_name != "Charters") %>%
arrange(desc(n_students)) %>%
head(15) %>%
mutate(county_label = reorder(county_name, n_students))
stopifnot(nrow(county_enr) > 0)
county_enr
#> # A tibble: 15 x 4
#> county_name n_students n_districts county_label
#> <chr> <dbl> <int> <fct>
#> 1 Bergen 132247 76 Bergen
#> 2 Essex 127986. 23 Essex
#> 3 Middlesex 124477 25 Middlesex
#> 4 Union 98046. 23 Union
#> 5 Monmouth 88726. 50 Monmouth
#> 6 Hudson 82525 13 Hudson
#> 7 Camden 79287 39 Camden
#> 8 Passaic 75967 21 Passaic
#> 9 Morris 72840. 40 Morris
#> 10 Burlington 68975 39 Burlington
#> 11 Ocean 65176. 28 Ocean
#> 12 Mercer 60305 12 Mercer
#> 13 Somerset 49703 19 Somerset
#> 14 Gloucester 46682. 28 Gloucester
#> 15 Atlantic 41422 24 Atlantic
13. Most NJ districts are tiny
Half of NJ’s 580 districts have fewer than 1,200 students. The median district is smaller than a single large high school.
dist_sizes <- enr_current %>%
filter(is_district, !is_charter,
subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(dist_sizes) > 0)
cat("Districts:", nrow(dist_sizes), "\n")
#> Districts: 580
cat("Median:", median(dist_sizes$n_students, na.rm = TRUE), "\n")
#> Median: 1180.5
cat("Under 1000:", sum(dist_sizes$n_students < 1000, na.rm = TRUE), "\n")
#> Under 1000: 264
cat("Over 10000:", sum(dist_sizes$n_students > 10000, na.rm = TRUE), "\n")
#> Over 10000: 16
14. NJ’s enrollment pyramid shows the pre-K boom
The 2025 grade-level distribution reveals the pre-K surge: PK enrollment (87K) is approaching K (89K), reflecting NJ’s universal pre-K push.
grade_enr <- state_summary %>%
filter(end_year == 2025, subgroup == "total_enrollment",
grade_level != "TOTAL", !is.na(grade_level)) %>%
mutate(grade_label = case_when(
grade_level == "PK" ~ "Pre-K",
grade_level == "K" ~ "K",
TRUE ~ paste("Grade", grade_level)
),
grade_order = case_when(
grade_level == "PK" ~ 0,
grade_level == "K" ~ 1,
TRUE ~ as.numeric(grade_level) + 1
)) %>%
arrange(grade_order) %>%
mutate(grade_label = factor(grade_label, levels = grade_label))
stopifnot(nrow(grade_enr) > 0)
grade_enr %>% select(grade_label, n_students)
#> # A tibble: 14 x 2
#> grade_label n_students
#> <fct> <dbl>
#> 1 Pre-K 87231
#> 2 K 89428
#> 3 Grade 01 94046
#> 4 Grade 02 95059
#> 5 Grade 03 97702
#> 6 Grade 04 96802
#> 7 Grade 05 98309
#> 8 Grade 06 99016
#> 9 Grade 07 100351
#> 10 Grade 08 101570
#> 11 Grade 09 104709
#> 12 Grade 10 105414.
#> 13 Grade 11 104496.
#> 14 Grade 12 107048
15. NJ’s poverty gap: Passaic vs Westfield
Passaic City has 98% of students on free/reduced lunch. Nearby Westfield has under 2%. This 96-point gap captures NJ’s extreme wealth inequality.
frl_all <- enr_current %>%
filter(is_district, !is_charter,
subgroup == "free_reduced_lunch", grade_level == "TOTAL",
!is.na(pct), n_students >= 100) %>%
arrange(desc(pct))
top_5 <- frl_all %>% head(5) %>% mutate(group = "Highest FRL")
bottom_5 <- frl_all %>% tail(5) %>% mutate(group = "Lowest FRL")
frl_extremes <- bind_rows(top_5, bottom_5) %>%
mutate(district_label = reorder(district_name, pct))
stopifnot(nrow(frl_extremes) > 0)
frl_extremes %>% select(district_name, n_students, pct, group)
#> # A tibble: 10 x 4
#> district_name n_students pct group
#> <chr> <dbl> <dbl> <chr>
#> 1 Passaic City School District 11540. 0.981 Highest FRL
#> 2 Kipp: Cooper Norcross, A New Jersey Nonprofit Corporation 2216. 0.972 Highest FRL
#> 3 Mastery Schools Of Camden, Inc. 2735. 0.952 Highest FRL
#> 4 Camden Prep, Inc. 1402. 0.936 Highest FRL
#> 5 Bridgeton City School District 5579. 0.923 Highest FRL
#> 6 Tenafly Public School District 112. 0.033 Lowest FRL
#> 7 Ridgewood Public School District 157. 0.029 Lowest FRL
#> 8 Bernards Township School District 111. 0.024 Lowest FRL
#> 9 Livingston Board Of Education School District 140. 0.022 Lowest FRL
#> 10 Westfield Public School District 105. 0.018 Lowest FRL