Overview

Recently I came across a post (https://7b813b34c0c64fe790e63e2fdb5a28d3.app.posit.cloud/#0) saying that North Carolina became one of the strongest population-growth states in the U.S. in 2023. North Carolina has actually experienced steady population growth throughout the past decade, which puts increasing pressure on its K–12 education system. This made me very interested in understanding how the system respond to this growing population.

Prepare

Since growth is not evently distributed across NC, I will only highlight examples to see how statewide patterns play out locally.I selected several counties in North Carolina that experienced the most fastest-growing county from 2010 to 2020 period (https://carolinademography.cpc.unc.edu/2021/08/12/first-look-at-2020-census-for-north-carolina/?utm_source=chatgpt.com), including Wake (+25.4%), Mecklenburg (+21.7%), Johnston (+27.9%), Cabarrus (+26.8%), and Brunswick (+27.2%). These counties show the most pronounced population expansion in the state and are located within its two major economic growth regions, the Triangle area and the Charlotte metropolitan area. Rapid in-migration in these regions has placed direct pressure on K–12 education systems, including increased school capacity needs, higher demand for teacher recruitment, and shifting student demographics. These counties therefore offer a clear perspective on how population growth shapes challenges and opportunities related to educational resources and educational equity.

According to the North Carolina Department of Public Instruction, the state operates 115 Local Education Agencies (LEAs), including 100 county-level districts. This means NC relies on large, county-based districts. Understanding how its educational infrastructure has expanded, or failed to expand, can reveal important insights about system capacity, equity, and long-term sustainability.

Research Questions

The central question guiding this investigation was:

What structural expansion patterns have emerged in North Carolina’s K–12 education system over the past decade (2012–2023), as reflected in changes to school counts, district sizes, grade-span configurations, and district types?

The goal is to map North Carolina’s expansion pathway using district-level structural indicators.

This analysis will benefit state policymakers, who can see whether current structures support ongoing growth. District and school leaders can gain a better understanding of long-term patterns of expansion and possible stress points. Educational researchers can examine how a large county-based system evolves under sustained population pressure. Community stakeholders, such as parents, can get contextual knowledge about district changes that affect school access and capacity.

Data Collection

To document North Carolina’s decade-long K–12 trends, I filtered the Education Data Explorer to extract district-level data for the 2012–2023 period. The dataset was limited to North Carolina public school districts and included core district characteristics. I focused on five of the state’s fastest-growing districts—Wake County Schools, Charlotte-Mecklenburg Schools, Johnston County Schools, Cabarrus County Schools, and Brunswick County Schools—to capture demographic and enrollment patterns associated with rapid population expansion.

Wrangle

Prepare the environment

library("tidyverse")
## Warning: package 'tidyverse' was built under R version 4.3.3
## Warning: package 'ggplot2' was built under R version 4.3.3
## Warning: package 'tibble' was built under R version 4.3.3
## Warning: package 'tidyr' was built under R version 4.3.3
## Warning: package 'readr' was built under R version 4.3.3
## Warning: package 'purrr' was built under R version 4.3.3
## Warning: package 'dplyr' was built under R version 4.3.3
## Warning: package 'stringr' was built under R version 4.3.3
## Warning: package 'forcats' was built under R version 4.3.3
## Warning: package 'lubridate' was built under R version 4.3.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Data cleaning

  1. The raw dataset included long or ambiguous variable names, and I renamed key variables to clearer and ready to analyze. Convert key variables to appropriate types;
library(readr)
nc_raw  <- read_csv("eci 586 final project/EducationDataPortal_12.04.2025_School Districts.csv")
## Rows: 60 Columns: 14
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (9): lea_name, state_location, city_location, agency_type, agency_level,...
## dbl (5): year, leaid, enrollment, highest_grade_offered, number_of_schools
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
nc_rename <- nc_raw |>
  rename(
    district_id      = leaid,
    district_name    = lea_name,
    state            = state_location,
    city             = city_location,
    district_type    = agency_type,
    total_enrollment = enrollment,
    district_level   = agency_level,
    charter_flag     = agency_charter_indicator,
    lowest_grade     = lowest_grade_offered,
    highest_grade    = highest_grade_offered,
    total_schools    = number_of_schools,
    bie_flag         = bureau_indian_education,
    boundary_change  = boundary_change_indicator
  ) |>
  mutate(
    lowest_grade = 0
  ) |>
    select(where(~ !all(is.na(.x))))
  1. Clean again to make all city and district name is the same:
library(stringr)

nc_clean <- nc_rename |>
  mutate(
    district_name = str_to_title(district_name),
    city = str_to_title(city)
  )
  1. Keep all school district names consistent
nc_clean <- nc_clean |>
  mutate(
    district_name = case_when(
      str_detect(district_name, regex("Johnston", ignore_case = TRUE)) ~ "Johnston County Schools",
      str_detect(district_name, regex("Cabarrus", ignore_case = TRUE)) ~ "Cabarrus County Schools",
      str_detect(district_name, regex("Brunswick", ignore_case = TRUE)) ~ "Brunswick County Schools",
      str_detect(district_name, regex("Charlotte[- ]?Mecklenburg", ignore_case = TRUE)) ~ "Charlotte-Mecklenburg Schools",
      str_detect(district_name, regex("Wake", ignore_case = TRUE)) ~ "Wake County Schools",
      TRUE ~ district_name
    ),
    city = str_to_title(city)
  )
  1. Create new variables to examine whether the district is expanding, including changes in the number of schools, student enrollment, school size, grade span, and attendance boundaries.
nc_features <- nc_clean |>
  arrange(district_name, year) |>
  mutate(
    # District structural indicators 
    avg_school_size = total_enrollment / total_schools,
    grade_span_range = highest_grade - lowest_grade,
    
    # Year-to-year change metrics 
    enrollment_change = total_enrollment - lag(total_enrollment),
    school_change     = total_schools - lag(total_schools),
    
    # Percent change (optional but useful)
    enrollment_pct_change = (enrollment_change / lag(total_enrollment)) * 100,
    school_pct_change     = (school_change / lag(total_schools)) * 100
  )
  1. Let’s create year-to-year trend variables.
library(dplyr)

nc_trend <- nc_features |>
  arrange(district_name, year) |>
  group_by(district_name) |>
  mutate(
       enroll_change = total_enrollment - lag(total_enrollment),
    school_change = total_schools     - lag(total_schools),
    enroll_pct_change = enroll_change / lag(total_enrollment) * 100,
    school_pct_change = school_change / lag(total_schools)   * 100
  ) |>
  ungroup()
nc_trend |>
  select(district_name, year,
         total_enrollment, enroll_change,
         total_schools, school_change,
         highest_grade, grade_span_range) |>
  head()
## # A tibble: 6 × 8
##   district_name  year total_enrollment enroll_change total_schools school_change
##   <chr>         <dbl>            <dbl>         <dbl>         <dbl>         <dbl>
## 1 Brunswick Co…  2012            12436            NA            19            NA
## 2 Brunswick Co…  2013            12415           -21            19             0
## 3 Brunswick Co…  2014            12534           119            19             0
## 4 Brunswick Co…  2015            12525            -9            19             0
## 5 Brunswick Co…  2016            12546            21            19             0
## 6 Brunswick Co…  2017            12603            57            19             0
## # ℹ 2 more variables: highest_grade <dbl>, grade_span_range <dbl>
  1. A summary from 2012 to 2023 based on year-by-year growth.
yearly_summary <- nc_trend |>
  group_by(district_name) |>
  summarise(
    avg_enroll_change = mean(enroll_change, na.rm = TRUE),
    avg_school_change = mean(school_change, na.rm = TRUE),
    years_increasing_enroll = sum(enroll_change > 0, na.rm = TRUE),
    years_increasing_schools = sum(school_change > 0, na.rm = TRUE)
  )

Visualization & Analysis

library(ggplot2)
ggplot(nc_trend, aes(x = year,
                     y = total_enrollment,
                     color = district_name,
                     group = district_name)) +
  geom_line() +
  geom_point() +
  labs(title = "Enrollment Trends by District (2012–2023)",
       x = "Year",
       y = "Total Enrollment",
       color = "District")

Result 1:

The visualization of district-level enrollment trends shows that structural expansion has occurred unevenly across districts. Wake County Schools exhibited continuous growth in overall district size across the decade, while Charlotte-Mecklenburg Schools experienced expansion followed by contraction and stabilization. Johnston and Cabarrus counties showed gradual incremental growth, and Brunswick County maintained a small but consistently expanding enrollment base. Collectively, these trends indicate that district-level structural expansion has been heterogeneous and non-linear, rather than uniform across the system.

ggplot(nc_trend, aes(x = year,
                     y = total_schools,
                     color = district_name,
                     group = district_name)) +
  geom_line() +
  geom_point() +
  labs(title = "School Count Trends by District (2012–2023)",
       x = "Year",
       y = "Total Schools",
       color = "District")

Result 2:

The visualization of school count trends by district reveals that structural expansion in North Carolina’s fast-growing districts primarily occurred through incremental increases in school counts. Across all five districts, the number of schools trended upward over the 2012–2023 period, although the pace of expansion varied substantially. Wake County Schools demonstrated the most pronounced expansion, while Charlotte-Mecklenburg Schools maintained steady but moderate growth. Johnston and Cabarrus counties exhibited gradual, stepwise increases, and Brunswick County remained relatively stable with only minimal expansion.

ggplot(nc_trend, aes(x = year,
                     y = enroll_change,
                     color = district_name,
                     group = district_name)) +
  geom_hline(yintercept = 0, linetype = "dashed") +
  geom_line() +
  geom_point() +
  labs(title = "Year-to-Year Enrollment Change",
       x = "Year",
       y = "Change in Enrollment",
       color = "District")
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_line()`).
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).

Result 3:

Most districts exhibited positive year-to-year enrollment growth between 2012 and 2023, indicating steady structural expansion in district size. Cabarrus, Johnston, and Wake Counties showed consistent incremental increases, while Brunswick remained relatively stable with small annual changes. In contrast, Charlotte-Mecklenburg Schools experienced substantial volatility, including a sharp enrollment decline in 2020, which aligns with the onset of the COVID-19 pandemic and widespread disruptions to schooling. Across all districts, 2020 functioned as a clear inflection point, followed by partial recovery and renewed growth in the post-pandemic period.

ggplot(nc_trend, aes(x = year,
                     y = school_change,
                     color = district_name,
                     group = district_name)) +
  geom_hline(yintercept = 0, linetype = "dashed") +
  geom_line() +
  geom_point() +
  labs(title = "Year-to-Year School Count Change",
       x = "Year",
       y = "Change in Number of Schools",
       color = "District")
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_line()`).
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).

Result 4:

The visualization shows that, across 2012–2023, most districts experienced small, positive year-to-year increases in school counts, consistent with a pattern of incremental structural expansion. However, a brief period of negative change occurred around 2016, most notably in Wake County and Charlotte-Mecklenburg Schools, indicating short-term school reconfiguration or consolidation rather than sustained contraction. Following this temporary dip, school counts resumed a steady upward trajectory. Overall, districts expanded primarily by gradually adding schools over time within stable district structures.

ggplot(yearly_summary,
       aes(x = district_name,
           y = avg_enroll_change)) +
  geom_col() +
  labs(title = "Average Yearly Enrollment Change by District",
       x = "District",
       y = "Avg Enrollment Change") +
  coord_flip()

Result 5:

Wake County Schools shows the largest average annual enrollment growth, indicating the strongest structural expansion. Cabarrus and Johnston Counties demonstrate moderate, steady growth, while Brunswick County shows only modest increases. Charlotte-Mecklenburg Schools displays near-zero average change, suggesting overall enrollment stability after pandemic-related losses. )

ggplot(yearly_summary,
       aes(x = district_name,
           y = avg_school_change)) +
  geom_col() +
  labs(title = "Average Yearly School Count Change by District",
       x = "District",
       y = "Avg School Change") +
  coord_flip()

Result 6:

Wake County Schools shows the highest average annual increase in school count, indicating the most active structural expansion. Charlotte-Mecklenburg and Cabarrus counties display moderate growth, while Johnston County expands more slowly. Brunswick County remains largely stable with minimal school count change.

Communication

Key Findings and Insights (in response to the RQ)

The results show that structural expansion in North Carolina’s K–12 system (2012–2023) occurred primarily through incremental increases in school counts and gradual enrollment growth, rather than through changes in district governance. Wake, Cabarrus, and Johnston counties exhibited the strongest expansion patterns, while Brunswick showed minimal change and Charlotte-Mecklenburg remained largely stable after pandemic-related losses. Grade-span configurations expanded modestly in 2014 (Pre-K–12 to Pre-K–13) and then stabilized. Importantly, district types and boundaries remained unchanged, indicating that expansion took place within stable administrative structures.

Suggested Actions

State and district leaders should use these findings to strengthen capacity planning, particularly in high-growth districts, by aligning school construction, staffing, and transportation strategies with predictable growth trends and potential future disruptions (e.g., pandemic-scale shocks). Researchers and policymakers should expand this approach to additional districts and link structural trends to equity and student outcome data to better understand the impact of expansion on educational access and quality.

Limitations, Ethics, and Acknowledgments

This analysis is limited to five fast-growing districts and relies on aggregate, publicly available data, which does not capture school-level or student-level disparities. The findings should not be interpreted as statewide generalizations. Ethical considerations included avoiding identification of individuals and presenting district trends responsibly to prevent stigmatization. Data were drawn from the Education Data Explorer (North Carolina, district-level data, 2012–2023) and its accompanying data dictionary.