Skip to contents
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", "growth" = "#27AE60", "decline" = "#E74C3C",
            "district1" = "#3498DB", "district2" = "#F39C12", "district3" = "#9B59B6",
            "district4" = "#1ABC9C", "district5" = "#E67E22")
# Get available years
years <- get_available_years()
if (is.list(years)) {
  max_year <- years$max_year
  min_year <- years$min_year
} else {
  max_year <- max(years)
  min_year <- min(years)
}

# Fetch data for various time spans
enr_all <- fetch_enr_multi(2002:max_year, use_cache = TRUE)
enr_decade <- fetch_enr_multi((max_year - 14):max_year, use_cache = TRUE)
enr_recent <- fetch_enr_multi((max_year - 9):max_year, use_cache = TRUE)
enr_current <- fetch_enr(max_year, use_cache = TRUE)

1. Idaho’s enrollment grew 28% in 24 years

Idaho is one of the fastest-growing states in America. Public school enrollment has surged since 2002 while many states are shrinking.

state_trend <- enr_all %>%
  filter(is_state, grade_level == "TOTAL", subgroup == "total_enrollment")
stopifnot(nrow(state_trend) > 0)

# Compute growth dynamically
first_yr <- state_trend %>% filter(end_year == min(end_year))
last_yr <- state_trend %>% filter(end_year == max(end_year))
growth <- last_yr$n_students - first_yr$n_students
pct_growth <- round((last_yr$n_students / first_yr$n_students - 1) * 100, 1)
yr_span <- max(state_trend$end_year) - min(state_trend$end_year)

state_trend %>% select(end_year, n_students)
#>    end_year n_students
#> 1      2002   246184.0
#> 2      2003   248660.0
#> 3      2004   250774.0
#> 4      2005   255980.0
#> 5      2006   262294.0
#> 6      2007   267630.0
#> 7      2008   272212.0
#> 8      2009   275113.0
#> 9      2010   278796.0
#> 10     2011   281632.0
#> 11     2012   281817.0
#> 12     2013   287620.0
#> 13     2014   289433.5
#> 14     2015   291198.0
#> 15     2016   294859.0
#> 16     2017   298765.0
#> 17     2018   302689.0
#> 18     2019   307228.0
#> 19     2020   311991.0
#> 20     2021   310653.0
#> 21     2022   316159.0
#> 22     2023   318979.0
#> 23     2024   318660.0
#> 24     2025   318067.0
#> 25     2026   314097.0
cat("Growth:", format(growth, big.mark = ","), "students (",
    pct_growth, "%) over", yr_span, "years\n")
#> Growth: 67,913 students ( 27.6 %) over 24 years

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 = "Idaho Public School Enrollment",
       subtitle = paste0("Added ", format(growth, big.mark = ","),
                        " students since 2002 (+", pct_growth, "%)"),
       x = "School Year", y = "Students") +
  theme_readme()

2. West Ada is Idaho’s school giant

West Ada School District in suburban Boise (Meridian/Eagle area) serves nearly 38,000 students - 75% larger than Boise itself. It’s the largest district in the state and still growing.

top_10 <- enr_current %>%
  filter(is_district, grade_level == "TOTAL", subgroup == "total_enrollment") %>%
  arrange(desc(n_students)) %>%
  head(10)
stopifnot(nrow(top_10) > 0)

# Compute ratio dynamically
west_ada <- top_10$n_students[1]
boise <- top_10$n_students[top_10$district_name == "BOISE INDEPENDENT DISTRICT"]
ratio_pct <- round((west_ada / boise - 1) * 100)

top_10 %>% select(district_name, n_students)
#>                          district_name n_students
#> 1                    WEST ADA DISTRICT      37919
#> 2           BOISE INDEPENDENT DISTRICT      21717
#> 3            BONNEVILLE JOINT DISTRICT      13511
#> 4                NAMPA SCHOOL DISTRICT      12473
#> 5                   POCATELLO DISTRICT      11437
#> 6             VALLIVUE SCHOOL DISTRICT      10700
#> 7                 IDAHO FALLS DISTRICT       9751
#> 8               COEUR D ALENE DISTRICT       9680
#> 9                  TWIN FALLS DISTRICT       8774
#> 10 IDAHO HOME LEARNING ACADEMY CHARTER       7504

ggplot(top_10, aes(x = reorder(district_name, n_students), y = n_students)) +
  geom_col(fill = colors["total"]) +
  coord_flip() +
  scale_y_continuous(labels = comma) +
  labs(title = "Idaho's Largest School Districts",
       subtitle = paste0("West Ada serves ", ratio_pct, "% more students than Boise"),
       x = "", y = "Students") +
  theme_readme()

3. The Treasure Valley boom

Eagle, Kuna, Star, and Middleton are among the fastest-growing communities in America. These districts around Boise have doubled or tripled enrollment in 15 years as families flee California.

treasure_valley <- c("WEST ADA DISTRICT", "KUNA JOINT DISTRICT", "MIDDLETON DISTRICT")
tv_trend <- enr_decade %>%
  filter(is_district,
         grepl(paste(treasure_valley, collapse = "|"), district_name),
         subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(tv_trend) > 0)
tv_trend %>% select(end_year, district_name, n_students)
#>    end_year       district_name n_students
#> 1      2012   WEST ADA DISTRICT      35188
#> 2      2012 KUNA JOINT DISTRICT       4983
#> 3      2012  MIDDLETON DISTRICT       3219
#> 4      2013   WEST ADA DISTRICT      35939
#> 5      2013 KUNA JOINT DISTRICT       5073
#> 6      2013  MIDDLETON DISTRICT       3362
#> 7      2014   WEST ADA DISTRICT      36111
#> 8      2014 KUNA JOINT DISTRICT       5102
#> 9      2014  MIDDLETON DISTRICT       3633
#> 10     2015   WEST ADA DISTRICT      36471
#> 11     2015 KUNA JOINT DISTRICT       5220
#> 12     2015  MIDDLETON DISTRICT       3768
#> 13     2016   WEST ADA DISTRICT      37366
#> 14     2016 KUNA JOINT DISTRICT       5283
#> 15     2016  MIDDLETON DISTRICT       3874
#> 16     2017   WEST ADA DISTRICT      38097
#> 17     2017 KUNA JOINT DISTRICT       5342
#> 18     2017  MIDDLETON DISTRICT       3920
#> 19     2018   WEST ADA DISTRICT      38907
#> 20     2018 KUNA JOINT DISTRICT       5397
#> 21     2018  MIDDLETON DISTRICT       4031
#> 22     2019   WEST ADA DISTRICT      39507
#> 23     2019 KUNA JOINT DISTRICT       5386
#> 24     2019  MIDDLETON DISTRICT       4086
#> 25     2020   WEST ADA DISTRICT      40326
#> 26     2020 KUNA JOINT DISTRICT       5618
#> 27     2020  MIDDLETON DISTRICT       4066
#> 28     2021   WEST ADA DISTRICT      37729
#> 29     2021 KUNA JOINT DISTRICT       5415
#> 30     2021  MIDDLETON DISTRICT       3912
#> 31     2022   WEST ADA DISTRICT      39027
#> 32     2022 KUNA JOINT DISTRICT       5738
#> 33     2022  MIDDLETON DISTRICT       4161
#> 34     2023   WEST ADA DISTRICT      39157
#> 35     2023 KUNA JOINT DISTRICT       5821
#> 36     2023  MIDDLETON DISTRICT       4344
#> 37     2024   WEST ADA DISTRICT      38670
#> 38     2024 KUNA JOINT DISTRICT       5800
#> 39     2024  MIDDLETON DISTRICT       4331
#> 40     2025   WEST ADA DISTRICT      38457
#> 41     2025 KUNA JOINT DISTRICT       5807
#> 42     2025  MIDDLETON DISTRICT       4323
#> 43     2026   WEST ADA DISTRICT      37919
#> 44     2026 KUNA JOINT DISTRICT       5699
#> 45     2026  MIDDLETON DISTRICT       4401

ggplot(tv_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) +
  scale_color_manual(values = c(colors["district1"], colors["district2"], colors["district3"])) +
  labs(title = "Treasure Valley Suburban Growth",
       subtitle = "West Ada, Kuna, and Middleton growing rapidly",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

Like most states, Idaho’s kindergarten enrollment has declined in recent years as birth rates drop statewide. The decline appears to have stabilized since 2021.

k_trend <- enr_decade %>%
  filter(is_state, subgroup == "total_enrollment", grade_level == "K")
stopifnot(nrow(k_trend) > 0)
k_trend %>% select(end_year, n_students)
#>    end_year n_students
#> 1      2012      22044
#> 2      2013      22537
#> 3      2014      22506
#> 4      2015      21555
#> 5      2016      21115
#> 6      2017      21148
#> 7      2018      21170
#> 8      2019      21487
#> 9      2020      21940
#> 10     2021      21127
#> 11     2022      22060
#> 12     2023      22042
#> 13     2024      20658
#> 14     2025      20787
#> 15     2026      20184

ggplot(k_trend, aes(x = end_year, y = n_students)) +
  geom_line(linewidth = 1.5, color = colors["growth"]) +
  geom_point(size = 3, color = colors["growth"]) +
  scale_y_continuous(labels = comma) +
  labs(title = "Idaho Kindergarten Enrollment",
       subtitle = "Decline stabilized since 2021",
       x = "School Year", y = "Students") +
  theme_readme()

5. COVID barely slowed Idaho

Most states lost 3-5% enrollment during the pandemic. Idaho dipped briefly in 2020, then resumed growing. Families moved TO Idaho during the pandemic, offsetting any losses.

covid_years <- enr_all %>%
  filter(is_state, grade_level == "TOTAL", subgroup == "total_enrollment",
         end_year >= 2018)
stopifnot(nrow(covid_years) > 0)
covid_years %>% select(end_year, n_students)
#>   end_year n_students
#> 1     2018     302689
#> 2     2019     307228
#> 3     2020     311991
#> 4     2021     310653
#> 5     2022     316159
#> 6     2023     318979
#> 7     2024     318660
#> 8     2025     318067
#> 9     2026     314097

ggplot(covid_years, 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 = 2021, linetype = "dashed", color = "red", alpha = 0.5) +
  annotate("text", x = 2021.3, y = max(covid_years$n_students, na.rm = TRUE) * 0.98,
           label = "COVID", hjust = 0, color = "red") +
  scale_y_continuous(labels = comma, limits = c(0, NA)) +
  labs(title = "Idaho Barely Slowed During COVID",
       subtitle = "Brief dip before resuming growth (2020-21 marked)",
       x = "School Year", y = "Students") +
  theme_readme()

6. Charter schools serve 1 in 14 Idaho students

Idaho has 32 charter schools serving nearly 23,000 students. Idaho Home Learning Academy alone accounts for over 7,500 students - a third of all charter enrollment.

charter <- enr_current %>%
  filter(is_charter, grade_level == "TOTAL", subgroup == "total_enrollment")
stopifnot(nrow(charter) > 0)

charter_total <- sum(charter$n_students, na.rm = TRUE)
state_total <- enr_current %>%
  filter(is_state, grade_level == "TOTAL", subgroup == "total_enrollment") %>%
  pull(n_students)
charter_ratio <- round(state_total / charter_total)

charter %>%
  arrange(desc(n_students)) %>%
  head(10) %>%
  select(district_name, n_students)
#>                          district_name n_students
#> 1  IDAHO HOME LEARNING ACADEMY CHARTER       7504
#> 2          INSPIRE VIRTUAL CHARTER LEA       1553
#> 3                   IDAHO ARTS CHARTER       1421
#> 4         COMPAS PUBLIC CHARTER SCHOOL       1289
#> 5                   NORTH STAR CHARTER       1143
#> 6                VISION CHARTER SCHOOL        757
#> 7                XAVIER CHARTER SCHOOL        675
#> 8            WHITE PINE CHARTER SCHOOL        653
#> 9                 ANSER CHARTER SCHOOL        598
#> 10       COEUR D'ALENE CHARTER ACADEMY        593

charter %>%
  arrange(desc(n_students)) %>%
  head(10) %>%
  ggplot(aes(x = reorder(district_name, n_students), y = n_students)) +
  geom_col(fill = colors["district3"]) +
  coord_flip() +
  scale_y_continuous(labels = comma) +
  labs(title = "Idaho's Largest Charter Schools",
       subtitle = paste0(format(charter_total, big.mark = ","),
                        " students in charters (",
                        round(charter_total / state_total * 100, 1),
                        "% of state, 1 in ", charter_ratio, ")"),
       x = "", y = "Students") +
  theme_readme()

7. Rural Idaho is emptying out

While Boise suburbs boom, northern and eastern Idaho are hollowing out. Wallace (Silver Valley), Salmon, and Challis have lost half their students in 20 years.

rural <- c("WALLACE DISTRICT", "SALMON DISTRICT", "CHALLIS JOINT DISTRICT")
rural_trend <- enr_all %>%
  filter(is_district,
         grepl(paste(rural, collapse = "|"), district_name),
         subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(rural_trend) > 0)
rural_trend %>% select(end_year, district_name, n_students)
#>    end_year          district_name n_students
#> 1      2002 CHALLIS JOINT DISTRICT        558
#> 2      2002        SALMON DISTRICT       1143
#> 3      2002       WALLACE DISTRICT        614
#> 4      2003 CHALLIS JOINT DISTRICT        520
#> 5      2003        SALMON DISTRICT       1112
#> 6      2003       WALLACE DISTRICT        599
#> 7      2004 CHALLIS JOINT DISTRICT        509
#> 8      2004        SALMON DISTRICT       1077
#> 9      2004       WALLACE DISTRICT        602
#> 10     2005 CHALLIS JOINT DISTRICT        461
#> 11     2005        SALMON DISTRICT       1060
#> 12     2005       WALLACE DISTRICT        555
#> 13     2006 CHALLIS JOINT DISTRICT        448
#> 14     2006        SALMON DISTRICT       1003
#> 15     2006       WALLACE DISTRICT        549
#> 16     2007 CHALLIS JOINT DISTRICT        462
#> 17     2007        SALMON DISTRICT        992
#> 18     2007       WALLACE DISTRICT        561
#> 19     2008 CHALLIS JOINT DISTRICT        467
#> 20     2008        SALMON DISTRICT        962
#> 21     2008       WALLACE DISTRICT        569
#> 22     2009 CHALLIS JOINT DISTRICT        439
#> 23     2009        SALMON DISTRICT        925
#> 24     2009       WALLACE DISTRICT        574
#> 25     2010 CHALLIS JOINT DISTRICT        411
#> 26     2010        SALMON DISTRICT        861
#> 27     2010       WALLACE DISTRICT        505
#> 28     2011 CHALLIS JOINT DISTRICT        430
#> 29     2011        SALMON DISTRICT        857
#> 30     2011       WALLACE DISTRICT        552
#> 31     2012 CHALLIS JOINT DISTRICT        439
#> 32     2012        SALMON DISTRICT        798
#> 33     2012       WALLACE DISTRICT        501
#> 34     2013 CHALLIS JOINT DISTRICT        433
#> 35     2013        SALMON DISTRICT        801
#> 36     2013       WALLACE DISTRICT        538
#> 37     2014 CHALLIS JOINT DISTRICT        408
#> 38     2014        SALMON DISTRICT        786
#> 39     2014       WALLACE DISTRICT        537
#> 40     2015 CHALLIS JOINT DISTRICT        403
#> 41     2015        SALMON DISTRICT        798
#> 42     2015       WALLACE DISTRICT        529
#> 43     2016 CHALLIS JOINT DISTRICT        376
#> 44     2016        SALMON DISTRICT        766
#> 45     2016       WALLACE DISTRICT        502
#> 46     2017 CHALLIS JOINT DISTRICT        360
#> 47     2017        SALMON DISTRICT        792
#> 48     2017       WALLACE DISTRICT        515
#> 49     2018 CHALLIS JOINT DISTRICT        354
#> 50     2018        SALMON DISTRICT        817
#> 51     2018       WALLACE DISTRICT        488
#> 52     2019 CHALLIS JOINT DISTRICT        362
#> 53     2019        SALMON DISTRICT        802
#> 54     2019       WALLACE DISTRICT        495
#> 55     2020 CHALLIS JOINT DISTRICT        324
#> 56     2020        SALMON DISTRICT        778
#> 57     2020       WALLACE DISTRICT        496
#> 58     2021 CHALLIS JOINT DISTRICT        307
#> 59     2021        SALMON DISTRICT        685
#> 60     2021       WALLACE DISTRICT        460
#> 61     2022 CHALLIS JOINT DISTRICT        345
#> 62     2022        SALMON DISTRICT        669
#> 63     2022       WALLACE DISTRICT        526
#> 64     2023 CHALLIS JOINT DISTRICT        333
#> 65     2023        SALMON DISTRICT        683
#> 66     2023       WALLACE DISTRICT        512
#> 67     2024 CHALLIS JOINT DISTRICT        345
#> 68     2024        SALMON DISTRICT        674
#> 69     2024       WALLACE DISTRICT        490
#> 70     2025 CHALLIS JOINT DISTRICT        343
#> 71     2025        SALMON DISTRICT        653
#> 72     2025       WALLACE DISTRICT        490
#> 73     2026 CHALLIS JOINT DISTRICT        329
#> 74     2026        SALMON DISTRICT        609
#> 75     2026       WALLACE DISTRICT        511

ggplot(rural_trend, aes(x = end_year, y = n_students, color = district_name)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2) +
  scale_y_continuous(labels = comma) +
  scale_color_manual(values = c(colors["decline"], colors["district2"], colors["district3"])) +
  labs(title = "Rural Idaho is Emptying Out",
       subtitle = "Northern mountain communities losing students",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

8. Boise proper declined while suburbs exploded

Boise Independent School District has declined to 22,000 students. Meanwhile, neighboring West Ada has grown from 20,000 to nearly 38,000. The growth is all in the suburbs.

boise_westada <- enr_all %>%
  filter(is_district,
         grepl("^BOISE INDEPENDENT DISTRICT|^WEST ADA DISTRICT", district_name),
         subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(boise_westada) > 0)
boise_westada %>% select(end_year, district_name, n_students)
#>    end_year              district_name n_students
#> 1      2002 BOISE INDEPENDENT DISTRICT      26321
#> 2      2002          WEST ADA DISTRICT      25061
#> 3      2003 BOISE INDEPENDENT DISTRICT      25931
#> 4      2003          WEST ADA DISTRICT      25940
#> 5      2004 BOISE INDEPENDENT DISTRICT      25694
#> 6      2004          WEST ADA DISTRICT      26045
#> 7      2005 BOISE INDEPENDENT DISTRICT      25681
#> 8      2005          WEST ADA DISTRICT      28005
#> 9      2006 BOISE INDEPENDENT DISTRICT      25142
#> 10     2006          WEST ADA DISTRICT      30326
#> 11     2007 BOISE INDEPENDENT DISTRICT      24900
#> 12     2007          WEST ADA DISTRICT      31603
#> 13     2008 BOISE INDEPENDENT DISTRICT      24996
#> 14     2008          WEST ADA DISTRICT      32728
#> 15     2009 BOISE INDEPENDENT DISTRICT      24896
#> 16     2009          WEST ADA DISTRICT      33578
#> 17     2010 BOISE INDEPENDENT DISTRICT      25205
#> 18     2010          WEST ADA DISTRICT      34398
#> 19     2011 BOISE INDEPENDENT DISTRICT      25269
#> 20     2011          WEST ADA DISTRICT      34625
#> 21     2012 BOISE INDEPENDENT DISTRICT      25247
#> 22     2012          WEST ADA DISTRICT      35188
#> 23     2013 BOISE INDEPENDENT DISTRICT      25440
#> 24     2013          WEST ADA DISTRICT      35939
#> 25     2014 BOISE INDEPENDENT DISTRICT      25978
#> 26     2014          WEST ADA DISTRICT      36111
#> 27     2015 BOISE INDEPENDENT DISTRICT      25916
#> 28     2015          WEST ADA DISTRICT      36471
#> 29     2016 BOISE INDEPENDENT DISTRICT      25891
#> 30     2016          WEST ADA DISTRICT      37366
#> 31     2017 BOISE INDEPENDENT DISTRICT      26175
#> 32     2017          WEST ADA DISTRICT      38097
#> 33     2018 BOISE INDEPENDENT DISTRICT      26048
#> 34     2018          WEST ADA DISTRICT      38907
#> 35     2019 BOISE INDEPENDENT DISTRICT      25527
#> 36     2019          WEST ADA DISTRICT      39507
#> 37     2020 BOISE INDEPENDENT DISTRICT      25484
#> 38     2020          WEST ADA DISTRICT      40326
#> 39     2021 BOISE INDEPENDENT DISTRICT      23854
#> 40     2021          WEST ADA DISTRICT      37729
#> 41     2022 BOISE INDEPENDENT DISTRICT      23362
#> 42     2022          WEST ADA DISTRICT      39027
#> 43     2023 BOISE INDEPENDENT DISTRICT      22885
#> 44     2023          WEST ADA DISTRICT      39157
#> 45     2024 BOISE INDEPENDENT DISTRICT      22496
#> 46     2024          WEST ADA DISTRICT      38670
#> 47     2025 BOISE INDEPENDENT DISTRICT      22230
#> 48     2025          WEST ADA DISTRICT      38457
#> 49     2026 BOISE INDEPENDENT DISTRICT      21717
#> 50     2026          WEST ADA DISTRICT      37919

ggplot(boise_westada, 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) +
  scale_color_manual(values = c(colors["district1"], colors["district2"])) +
  labs(title = "Boise Flat, West Ada Exploding",
       subtitle = "Suburban growth outpacing the urban core",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

9. High school enrollment surged 22% in 15 years

Idaho’s high schools are seeing record enrollment as the wave of growth works through the grades.

hs_trend <- enr_decade %>%
  filter(is_state, subgroup == "total_enrollment",
         grade_level %in% c("09", "10", "11", "12")) %>%
  group_by(end_year) %>%
  summarize(n_students = sum(n_students, na.rm = TRUE), .groups = "drop")
stopifnot(nrow(hs_trend) > 0)

# Compute growth dynamically
hs_first <- hs_trend$n_students[1]
hs_last <- hs_trend$n_students[nrow(hs_trend)]
hs_pct <- round((hs_last / hs_first - 1) * 100)

hs_trend
#> # A tibble: 15 × 2
#>    end_year n_students
#>       <int>      <dbl>
#>  1     2012     83121 
#>  2     2013     83856 
#>  3     2014     84548.
#>  4     2015     85212 
#>  5     2016     87524 
#>  6     2017     89206 
#>  7     2018     90845 
#>  8     2019     92165 
#>  9     2020     94603 
#> 10     2021     97596 
#> 11     2022     98764 
#> 12     2023    100362 
#> 13     2024    101530 
#> 14     2025    102039 
#> 15     2026    101402
cat("HS growth:", format(hs_first, big.mark = ","), "->",
    format(hs_last, big.mark = ","), "(+", hs_pct, "%)\n")
#> HS growth: 83,121 -> 101,402 (+ 22 %)

ggplot(hs_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 = "Idaho High School Enrollment (Grades 9-12)",
       subtitle = paste0("From ", format(hs_first, big.mark = ","), " to ",
                        format(hs_last, big.mark = ","), " (+", hs_pct, "%)"),
       x = "School Year", y = "Students") +
  theme_readme()

10. The Magic Valley is holding steady

Twin Falls, Jerome, and Minidoka County in south-central Idaho have maintained steady enrollment even as some rural areas shrink. Agriculture and food processing anchor these communities.

magic_valley <- c("TWIN FALLS DISTRICT", "JEROME JOINT DISTRICT", "MINIDOKA COUNTY JOINT DISTRICT")
mv_trend <- enr_decade %>%
  filter(is_district,
         grepl(paste(magic_valley, collapse = "|"), district_name),
         !grepl("VIRTUAL|CHARTER", district_name),
         subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(mv_trend) > 0)
mv_trend %>% select(end_year, district_name, n_students)
#>    end_year                  district_name n_students
#> 1      2012          JEROME JOINT DISTRICT       3428
#> 2      2012 MINIDOKA COUNTY JOINT DISTRICT       4042
#> 3      2012            TWIN FALLS DISTRICT       7791
#> 4      2013          JEROME JOINT DISTRICT       3590
#> 5      2013 MINIDOKA COUNTY JOINT DISTRICT       4016
#> 6      2013            TWIN FALLS DISTRICT       8265
#> 7      2014          JEROME JOINT DISTRICT       3645
#> 8      2014 MINIDOKA COUNTY JOINT DISTRICT       4098
#> 9      2014            TWIN FALLS DISTRICT       8565
#> 10     2015          JEROME JOINT DISTRICT       3752
#> 11     2015 MINIDOKA COUNTY JOINT DISTRICT       4127
#> 12     2015            TWIN FALLS DISTRICT       8805
#> 13     2016          JEROME JOINT DISTRICT       3830
#> 14     2016 MINIDOKA COUNTY JOINT DISTRICT       4180
#> 15     2016            TWIN FALLS DISTRICT       9051
#> 16     2017          JEROME JOINT DISTRICT       3933
#> 17     2017 MINIDOKA COUNTY JOINT DISTRICT       4232
#> 18     2017            TWIN FALLS DISTRICT       9194
#> 19     2018          JEROME JOINT DISTRICT       4014
#> 20     2018 MINIDOKA COUNTY JOINT DISTRICT       4222
#> 21     2018            TWIN FALLS DISTRICT       9478
#> 22     2019          JEROME JOINT DISTRICT       4062
#> 23     2019 MINIDOKA COUNTY JOINT DISTRICT       4257
#> 24     2019            TWIN FALLS DISTRICT       9488
#> 25     2020          JEROME JOINT DISTRICT       4141
#> 26     2020 MINIDOKA COUNTY JOINT DISTRICT       4315
#> 27     2020            TWIN FALLS DISTRICT       9620
#> 28     2021          JEROME JOINT DISTRICT       4076
#> 29     2021 MINIDOKA COUNTY JOINT DISTRICT       4264
#> 30     2021            TWIN FALLS DISTRICT       9180
#> 31     2022          JEROME JOINT DISTRICT       4151
#> 32     2022 MINIDOKA COUNTY JOINT DISTRICT       4480
#> 33     2022            TWIN FALLS DISTRICT       9412
#> 34     2023          JEROME JOINT DISTRICT       4155
#> 35     2023 MINIDOKA COUNTY JOINT DISTRICT       4420
#> 36     2023            TWIN FALLS DISTRICT       9362
#> 37     2024          JEROME JOINT DISTRICT       4082
#> 38     2024 MINIDOKA COUNTY JOINT DISTRICT       4414
#> 39     2024            TWIN FALLS DISTRICT       9241
#> 40     2025          JEROME JOINT DISTRICT       4006
#> 41     2025 MINIDOKA COUNTY JOINT DISTRICT       4293
#> 42     2025            TWIN FALLS DISTRICT       9024
#> 43     2026          JEROME JOINT DISTRICT       3862
#> 44     2026 MINIDOKA COUNTY JOINT DISTRICT       4114
#> 45     2026            TWIN FALLS DISTRICT       8774

ggplot(mv_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) +
  scale_color_manual(values = c(colors["district1"], colors["district2"], colors["district3"])) +
  labs(title = "Magic Valley Districts Holding Steady",
       subtitle = "Twin Falls and Jerome maintain enrollment",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

11. Elementary is the biggest cohort

Idaho’s elementary schools (K-5) serve the largest share of students. The grade distribution shows a healthy pipeline with growing younger grades - a sign of continued future growth.

grades <- enr_current %>%
  filter(is_state, subgroup == "total_enrollment",
         grade_level %in% c("K", "01", "02", "03", "04", "05",
                           "06", "07", "08", "09", "10", "11", "12"))
stopifnot(nrow(grades) > 0)

grade_order <- c("K", "01", "02", "03", "04", "05",
                 "06", "07", "08", "09", "10", "11", "12")
grades$grade_level <- factor(grades$grade_level, levels = grade_order)

grades %>% select(grade_level, n_students)
#>    grade_level n_students
#> 1            K      20184
#> 2           01      21286
#> 3           02      21524
#> 4           03      23354
#> 5           04      24147
#> 6           05      24290
#> 7           06      24539
#> 8           07      24756
#> 9           08      24762
#> 10          09      25474
#> 11          10      25413
#> 12          11      25199
#> 13          12      25316

ggplot(grades, aes(x = grade_level, y = n_students)) +
  geom_col(fill = colors["total"]) +
  scale_y_continuous(labels = comma) +
  labs(title = "Idaho Enrollment by Grade Level",
       subtitle = "Strong elementary enrollment signals continued growth",
       x = "Grade", y = "Students") +
  theme_readme()

12. Coeur d’Alene leads northern Idaho

In the Idaho panhandle, Coeur d’Alene School District leads with nearly 10,000 students. Post Falls and Lakeland are also growing as people relocate from Spokane and beyond.

north <- c("COEUR D ALENE DISTRICT", "POST FALLS DISTRICT", "LAKELAND DISTRICT")
north_trend <- enr_decade %>%
  filter(is_district,
         grepl(paste(north, collapse = "|"), district_name),
         subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(north_trend) > 0)
north_trend %>% select(end_year, district_name, n_students)
#>    end_year          district_name n_students
#> 1      2012 COEUR D ALENE DISTRICT      10107
#> 2      2012      LAKELAND DISTRICT       4340
#> 3      2012    POST FALLS DISTRICT       5626
#> 4      2013 COEUR D ALENE DISTRICT      10173
#> 5      2013      LAKELAND DISTRICT       4164
#> 6      2013    POST FALLS DISTRICT       5697
#> 7      2014 COEUR D ALENE DISTRICT      10284
#> 8      2014      LAKELAND DISTRICT       4130
#> 9      2014    POST FALLS DISTRICT       5706
#> 10     2015 COEUR D ALENE DISTRICT      10458
#> 11     2015      LAKELAND DISTRICT       4199
#> 12     2015    POST FALLS DISTRICT       5629
#> 13     2016 COEUR D ALENE DISTRICT      10711
#> 14     2016      LAKELAND DISTRICT       4215
#> 15     2016    POST FALLS DISTRICT       5718
#> 16     2017 COEUR D ALENE DISTRICT      10700
#> 17     2017      LAKELAND DISTRICT       4381
#> 18     2017    POST FALLS DISTRICT       5809
#> 19     2018 COEUR D ALENE DISTRICT      10836
#> 20     2018      LAKELAND DISTRICT       4371
#> 21     2018    POST FALLS DISTRICT       5897
#> 22     2019 COEUR D ALENE DISTRICT      10888
#> 23     2019      LAKELAND DISTRICT       4474
#> 24     2019    POST FALLS DISTRICT       6055
#> 25     2020 COEUR D ALENE DISTRICT      11075
#> 26     2020      LAKELAND DISTRICT       4590
#> 27     2020    POST FALLS DISTRICT       6175
#> 28     2021 COEUR D ALENE DISTRICT      10061
#> 29     2021      LAKELAND DISTRICT       4320
#> 30     2021    POST FALLS DISTRICT       5842
#> 31     2022 COEUR D ALENE DISTRICT      10191
#> 32     2022      LAKELAND DISTRICT       4671
#> 33     2022    POST FALLS DISTRICT       6194
#> 34     2023 COEUR D ALENE DISTRICT      10137
#> 35     2023      LAKELAND DISTRICT       4768
#> 36     2023    POST FALLS DISTRICT       6069
#> 37     2024 COEUR D ALENE DISTRICT       9677
#> 38     2024      LAKELAND DISTRICT       4602
#> 39     2024    POST FALLS DISTRICT       5911
#> 40     2025 COEUR D ALENE DISTRICT       9667
#> 41     2025      LAKELAND DISTRICT       4647
#> 42     2025    POST FALLS DISTRICT       5823
#> 43     2026 COEUR D ALENE DISTRICT       9680
#> 44     2026      LAKELAND DISTRICT       4649
#> 45     2026    POST FALLS DISTRICT       5838

ggplot(north_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) +
  scale_color_manual(values = c(colors["district1"], colors["district2"], colors["district3"])) +
  labs(title = "North Idaho District Growth",
       subtitle = "Coeur d'Alene, Post Falls, and Lakeland expanding",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

13. Small districts dominate the landscape

Idaho has 115+ school districts, but many are tiny. Over 40 districts have fewer than 500 students. These small rural districts face unique challenges with limited resources.

district_sizes <- enr_current %>%
  filter(is_district, grade_level == "TOTAL", subgroup == "total_enrollment") %>%
  mutate(size_category = case_when(
    n_students >= 10000 ~ "10,000+",
    n_students >= 5000 ~ "5,000-9,999",
    n_students >= 2000 ~ "2,000-4,999",
    n_students >= 1000 ~ "1,000-1,999",
    n_students >= 500 ~ "500-999",
    TRUE ~ "Under 500"
  )) %>%
  group_by(size_category) %>%
  summarize(n_districts = n(), .groups = "drop")
stopifnot(nrow(district_sizes) > 0)

district_sizes$size_category <- factor(district_sizes$size_category,
  levels = c("Under 500", "500-999", "1,000-1,999", "2,000-4,999", "5,000-9,999", "10,000+"))

district_sizes
#> # A tibble: 6 × 2
#>   size_category n_districts
#>   <fct>               <int>
#> 1 1,000-1,999            23
#> 2 10,000+                 6
#> 3 2,000-4,999            19
#> 4 5,000-9,999             9
#> 5 500-999                37
#> 6 Under 500              98

ggplot(district_sizes, aes(x = size_category, y = n_districts)) +
  geom_col(fill = colors["total"]) +
  labs(title = "Idaho Districts by Size",
       subtitle = "Many small rural districts, few large ones",
       x = "District Size (Students)", y = "Number of Districts") +
  theme_readme()

14. Nampa and Caldwell anchor Canyon County

Nampa School District and Caldwell serve Canyon County west of Boise. Both have grown with the Treasure Valley, though Nampa has seen recent declines from its 2014 peak.

canyon <- c("NAMPA SCHOOL DISTRICT", "CALDWELL DISTRICT")
canyon_trend <- enr_decade %>%
  filter(is_district,
         grepl(paste(canyon, collapse = "|"), district_name),
         subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(canyon_trend) > 0)
canyon_trend %>% select(end_year, district_name, n_students)
#>    end_year         district_name n_students
#> 1      2012 NAMPA SCHOOL DISTRICT      15135
#> 2      2012     CALDWELL DISTRICT       5961
#> 3      2013 NAMPA SCHOOL DISTRICT      15776
#> 4      2013     CALDWELL DISTRICT       6151
#> 5      2014 NAMPA SCHOOL DISTRICT      15044
#> 6      2014     CALDWELL DISTRICT       6277
#> 7      2015 NAMPA SCHOOL DISTRICT      14901
#> 8      2015     CALDWELL DISTRICT       6173
#> 9      2016 NAMPA SCHOOL DISTRICT      14848
#> 10     2016     CALDWELL DISTRICT       6300
#> 11     2017 NAMPA SCHOOL DISTRICT      14341
#> 12     2017     CALDWELL DISTRICT       6338
#> 13     2018 NAMPA SCHOOL DISTRICT      14470
#> 14     2018     CALDWELL DISTRICT       6424
#> 15     2019 NAMPA SCHOOL DISTRICT      13977
#> 16     2019     CALDWELL DISTRICT       6407
#> 17     2020 NAMPA SCHOOL DISTRICT      14039
#> 18     2020     CALDWELL DISTRICT       6125
#> 19     2021 NAMPA SCHOOL DISTRICT      13230
#> 20     2021     CALDWELL DISTRICT       5595
#> 21     2022 NAMPA SCHOOL DISTRICT      13657
#> 22     2022     CALDWELL DISTRICT       5596
#> 23     2023 NAMPA SCHOOL DISTRICT      13418
#> 24     2023     CALDWELL DISTRICT       5508
#> 25     2024 NAMPA SCHOOL DISTRICT      13150
#> 26     2024     CALDWELL DISTRICT       5401
#> 27     2025 NAMPA SCHOOL DISTRICT      12729
#> 28     2025     CALDWELL DISTRICT       5142
#> 29     2026 NAMPA SCHOOL DISTRICT      12473
#> 30     2026     CALDWELL DISTRICT       4932

ggplot(canyon_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) +
  scale_color_manual(values = c(colors["district1"], colors["district2"])) +
  labs(title = "Canyon County Districts",
       subtitle = "Nampa and Caldwell growing with Treasure Valley",
       x = "School Year", y = "Students", color = "") +
  theme_readme()

15. Idaho Falls leads eastern Idaho

Idaho Falls School District (#91) anchors the eastern region with nearly 10,000 students. Nearby Bonneville Joint District serves the growing suburban areas with 13,500 students. Eastern Idaho grows more slowly than the Treasure Valley.

eastern <- c("IDAHO FALLS DISTRICT", "BONNEVILLE JOINT DISTRICT")
eastern_trend <- enr_decade %>%
  filter(is_district,
         grepl(paste(eastern, collapse = "|"), district_name),
         subgroup == "total_enrollment", grade_level == "TOTAL")
stopifnot(nrow(eastern_trend) > 0)
eastern_trend %>% select(end_year, district_name, n_students)
#>    end_year             district_name n_students
#> 1      2012      IDAHO FALLS DISTRICT      10197
#> 2      2012 BONNEVILLE JOINT DISTRICT      10359
#> 3      2013      IDAHO FALLS DISTRICT      10499
#> 4      2013 BONNEVILLE JOINT DISTRICT      10834
#> 5      2014      IDAHO FALLS DISTRICT      10263
#> 6      2014 BONNEVILLE JOINT DISTRICT      11149
#> 7      2015      IDAHO FALLS DISTRICT      10391
#> 8      2015 BONNEVILLE JOINT DISTRICT      11870
#> 9      2016      IDAHO FALLS DISTRICT      10410
#> 10     2016 BONNEVILLE JOINT DISTRICT      11888
#> 11     2017      IDAHO FALLS DISTRICT      10257
#> 12     2017 BONNEVILLE JOINT DISTRICT      12224
#> 13     2018      IDAHO FALLS DISTRICT      10167
#> 14     2018 BONNEVILLE JOINT DISTRICT      12527
#> 15     2019      IDAHO FALLS DISTRICT      10213
#> 16     2019 BONNEVILLE JOINT DISTRICT      12905
#> 17     2020      IDAHO FALLS DISTRICT      10272
#> 18     2020 BONNEVILLE JOINT DISTRICT      13325
#> 19     2021      IDAHO FALLS DISTRICT      10005
#> 20     2021 BONNEVILLE JOINT DISTRICT      13224
#> 21     2022      IDAHO FALLS DISTRICT      10194
#> 22     2022 BONNEVILLE JOINT DISTRICT      13481
#> 23     2023      IDAHO FALLS DISTRICT      10255
#> 24     2023 BONNEVILLE JOINT DISTRICT      13801
#> 25     2024      IDAHO FALLS DISTRICT      10289
#> 26     2024 BONNEVILLE JOINT DISTRICT      13663
#> 27     2025      IDAHO FALLS DISTRICT       9935
#> 28     2025 BONNEVILLE JOINT DISTRICT      13604
#> 29     2026      IDAHO FALLS DISTRICT       9751
#> 30     2026 BONNEVILLE JOINT DISTRICT      13511

ggplot(eastern_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) +
  scale_color_manual(values = c(colors["district1"], colors["district2"])) +
  labs(title = "Eastern Idaho Districts",
       subtitle = "Idaho Falls and Bonneville anchor the region",
       x = "School Year", y = "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      idschooldata_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  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         httr_1.4.8         rmarkdown_2.30    
#> [45] purrr_1.2.1        tools_4.5.2        pkgconfig_2.0.3    htmltools_0.5.9