Basic usage of edfinr

Introduction

The edfinr package provides tidy, analysis-ready school district finance data for the United States — NCES F-33 revenues and expenditures joined with enrollment, poverty, community, and labor-cost measures — assembled with an opinionated cleaning methodology. This vignette will help you get started with the package’s core functionality.

library(edfinr)
library(dplyr)
library(ggplot2)

Core function: get_finance_data()

The primary function in edfinr is get_finance_data(), which provides access to school finance data from school years 2011-12 through 2022-23. NCES F-33 data are released roughly two years after a fiscal year closes, so FY2023 (SY2022-23) is the most recent federal release. The function combines data from multiple sources:

Basic usage

The simplest way to use get_finance_data() is to specify a year and state. For example, to get finance data for Kentucky school districts from the 2022-23 school year:

ky_sy23 <- get_finance_data(yr = "2023", geo = "KY")
glimpse(ky_sy23)

Dataset types: skinny vs. full

By default, get_finance_data() returns a “skinny” dataset with 59 essential variables covering:

For more detailed analysis, you can request the “full” dataset with 124 variables that includes:

ky_full_sy23 <- get_finance_data(yr = "2023", geo = "KY", dataset_type = "full")
setdiff(names(ky_full_sy23), names(ky_sy23))

Finding variables

With 124 variables in the full dataset, the data dictionary is the fastest way to find what you need. list_variables() returns it as a tibble, so you can filter and search it like any other data.

vars <- list_variables("full")
vars
## # A tibble: 124 × 7
##    name         type      category    source f33_item first_yr_avail description
##    <chr>        <chr>     <chr>       <chr>  <chr>    <chr>          <chr>      
##  1 ncesid       character id          NCES … LEAID    2012           NCES distr…
##  2 year         integer   time        NCES … YRDATA   2012           School yea…
##  3 state        character geographic  NCES … STATE    2012           State abbr…
##  4 dist_name    character id          NCES … NAME     2012           District n…
##  5 enroll       numeric   demographic NCES … V33      2012           Total dist…
##  6 rev_total_pp numeric   revenue     NCES … <NA>     2012           Total adju…
##  7 rev_local_pp numeric   revenue     NCES … <NA>     2012           Local adju…
##  8 rev_state_pp numeric   revenue     NCES … <NA>     2012           State adju…
##  9 rev_fed_pp   numeric   revenue     NCES … <NA>     2012           Federal ad…
## 10 rev_total    numeric   revenue     NCES … <NA>     2012           Total adju…
## # ℹ 114 more rows
# filter by category
list_variables("full", category = "debt")
## # A tibble: 9 × 7
##   name              type    category source  f33_item first_yr_avail description
##   <chr>             <chr>   <chr>    <chr>   <chr>    <chr>          <chr>      
## 1 debt_lt_begin     numeric debt     NCES F… _19H     2012           Long-term …
## 2 debt_lt_issued    numeric debt     NCES F… _21F     2012           Long-term …
## 3 debt_lt_retired   numeric debt     NCES F… _31F     2012           Long-term …
## 4 debt_lt_end       numeric debt     NCES F… _41F     2012           Long-term …
## 5 debt_st_begin     numeric debt     NCES F… _61V     2012           Short-term…
## 6 debt_st_end       numeric debt     NCES F… _66V     2012           Short-term…
## 7 fund_bal_debt_svc numeric debt     NCES F… W01      2012           Debt servi…
## 8 fund_bal_bond     numeric debt     NCES F… W31      2012           Bond fund …
## 9 fund_bal_other    numeric debt     NCES F… W61      2012           Other fund…

Multiple years and states

The get_finance_data() function makes it easy to access data across multiple years and states:

sec_data <- get_finance_data(
  yr = "2019:2023",  # years 2019 through 2023
  geo = "AL,AR,FL,GA,KY,LA,MS,MO,OK,SC,TN,TX"  # comma-separated state codes
)

us_sy23 <- get_finance_data(yr = "2023", geo = "all")

Downloads and caching

Only the requested year(s) are downloaded: each year is hosted as its own file (roughly 3-6 MB), so a single-year or short-range request is lightweight even though the full panel spans 2012-2023. Requesting yr = "all" downloads the entire history from one combined file.

Downloaded files are cached in R’s temporary directory for the length of your R session, so repeated calls with the same years re-read the cache instead of re-downloading. Two arguments control this behavior: refresh = TRUE forces a fresh download (for example, after a data update is announced), and quiet = TRUE suppresses the download progress messages.

# re-download even if a cached copy exists, without progress messages
ky_fresh <- get_finance_data(yr = "2023", geo = "KY", refresh = TRUE, quiet = TRUE)

Working with the data

Once you’ve retrieved the data, you can use standard data manipulation tools to analyze it. Here are some common analysis patterns:

Do high local revenue share districts end up with more total revenue?

ct_sy23 <- get_finance_data(yr = "2023", geo = "CT")

ggplot(ct_sy23) +
  geom_point(aes(
    x = rev_local / rev_total,
    y = rev_total_pp,
    color = urbanicity,
    size = enroll),
    alpha = .6) +
  scale_size_area(
    max_size = 10,
    labels = scales::label_comma()
    ) +
  scale_x_continuous(labels = scales::label_percent()) +
  scale_y_continuous(labels = scales::label_dollar()) +
  labs(
    title = "Connecticut Districts' Local Revenue Share vs. Total Revenue Per-Pupil, SY2022-23",
    x = "Local Share of Total Revenue",
    y = "Total Revenue Per-Pupil",
    size = "Enrollment",
    color = "Urbanicity") +
  theme_bw()

How do revenue sources differ by urbanicity?

# compare revenue mix across urbanicity groups (dollar-weighted)
revenue_analysis <- ct_sy23 |>
  group_by(urbanicity) |>
  summarize(
    pct_local = sum(rev_local, na.rm = TRUE) / sum(rev_total, na.rm = TRUE),
    pct_state = sum(rev_state, na.rm = TRUE) / sum(rev_total, na.rm = TRUE),
    pct_federal = sum(rev_fed, na.rm = TRUE) / sum(rev_total, na.rm = TRUE),
    n_districts = n(),
    enrollment = sum(enroll, na.rm = TRUE)
  )

revenue_analysis

See also