The Texas House passed Senate Bill 160 in 2017, designed to terminate all school districts from imposing an 8.5 percent cap on the number of students who can enroll in special education. By the end of 2021, the number of school districts in the state that offer limited special education services went down to 112, around one-tenth of all districts.

In 2016, the Houston Chronicle published a multi-part series called Denied that outlined how a Texas Education Agency policy started in 2004 could force an audit of schools that have more than 8.5% of their students in special education programs.

Following the Chronicle’s reporting (along with other news orgs), the Texas Legislature in 2017 unanimously banned using a target or benchmark on how many students a district or charter school enrolls in special education.

According to Texas Academic Performance Reports from 2013 to 2021, the impacts of the bill have been phenomenal. Among 1,021 school districts in Texas, the number of districts that have not delayed or denied special education to disabled students went up from 591 to 909, a nearly 54% increase, by the end of 2021.

The year of 2017 became a big turning point. Over the past five years, the fluctuation in the number of schools that went below the 8.5 percent benchmark had never exceeded 20. However, during governmental implementation, the number of schools receiving special education ballooned by 51.

Although the Texas Education Agency reports that the number of students receiving special education services is escalating, the state remains below the national average of 13 percent five years after the cap was lifted. The number has gone from 8.6 percent in the 2012-2013 school year to 11.3 percent for the 2020-2021 school year.

About the data

Each year, the Texas Education Agency publishes the percentage of students in special education as part of their Texas Academic Performance Reports.

Setting up

library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.3.6     ✔ purrr   0.3.4
## ✔ tibble  3.1.8     ✔ dplyr   1.0.9
## ✔ tidyr   1.2.0     ✔ stringr 1.4.1
## ✔ readr   2.1.2     ✔ forcats 0.5.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(scales)
## 
## Attaching package: 'scales'
## 
## The following object is masked from 'package:purrr':
## 
##     discard
## 
## The following object is masked from 'package:readr':
## 
##     col_factor
library(DT)

Import cleaned data

sped <- read_rds("data-processed/01-sped.rds")

sped  |> head()

Creating a searchable table with pivot_wide function

district_percents_data <- sped |> 
  select(distname, cntyname, year, sped_percent) |> 
  pivot_wider(names_from = year, values_from = sped_percent)


district_percents_data

Creating a datatable

district_percents_data |> 
  datatable()

Build the statewide percentage by year data

yearly_percent <- sped |> 
  group_by(year) |> 
  summarise(
    total_students = sum(all_count),
    total_sped = sum(sped_count)
  ) |> 
  mutate(sped_percent = ((total_sped / total_students) * 100) |> round(1))

yearly_percent

Building the statewide percentage chart

yearly_percent |> 
  ggplot(aes(x = year, y = sped_percent)) + 
  geom_col() +
  geom_text(aes(label = sped_percent, vjust = -.5))

Build the audit flag data

flag_count_districts <- sped |> 
  count(year, audit_flag, name = "count_districts")

flag_count_districts

Build the audit flag chart

flag_count_districts |> 
  ggplot(aes(x = year, y = count_districts, fill = audit_flag)) +
  geom_col()

flag_count_districts |> 
  ggplot(aes(x = year, y = count_districts, fill = audit_flag)) +
  geom_col(position = "dodge")

flag_count_districts |> 
  ggplot(aes(x = year, y = count_districts, group = audit_flag)) +
  geom_line(aes(color = audit_flag)) +
  ylim(0,1000)

Changes in local districts

change_bastrop <- sped |> 
  filter(cntyname == "BASTROP") |> 
  ggplot(aes(x = year, y = sped_percent, group = distname)) +
  geom_line(aes(color = distname))

change_bastrop

change_hays <- sped |> 
  filter(cntyname == "HAYS") |> 
  ggplot(aes(x = year, y = sped_percent, group = distname)) +
  geom_line(aes(color = distname))

change_hays

change_travis <- sped |> 
  filter(cntyname == "TRAVIS") |> 
  ggplot(aes(x = year, y = sped_percent, group = distname)) +
  geom_line(aes(color = distname))

change_travis

change_williamson <- sped |> 
  filter(cntyname == "WILLIAMSON") |> 
  ggplot(aes(x = year, y = sped_percent, group = distname)) +
  geom_line(aes(color = distname))

change_williamson