When analyzing education finance data across multiple years,
adjusting for inflation can help produce more meaningful comparisons.
edfinr provides built-in functionality to adjust
dollar-denominated flows (revenues, expenditures, and income measures)
for inflation using the Consumer Price Index for All Urban Consumers
(CPI-U). See “What gets adjusted” below for the exact scope.
By default, all financial data returned by
get_finance_data() is in nominal dollars -
the actual dollar amounts reported in each year without any inflation
adjustment. This means that $1,000 in 2012 and $1,000 in 2023 are
treated as equal amounts, even though they have different purchasing
power.
To make valid comparisons across years, you need to convert to real dollars (also called constant dollars) by adjusting for inflation.
edfinr uses the CPI-U index to adjust for inflation. The
adjustment is aligned to the school year calendar:
For example, the 2022-23 school year CPI combines:
The get_finance_data() function includes a
cpi_adj parameter to automatically adjust
dollar-denominated flows:
# Get nominal (unadjusted) data - this is the default
nominal_data <- get_finance_data(yr = "2016:2023", geo = "KY")
# View the nominal revenue for a specific district
nominal_data |>
filter(dist_name == "Jefferson County") |>
select(year, dist_name, rev_total, rev_total_pp)# Get data adjusted to 2023 dollars
real_2023_data <- get_finance_data(yr = "2016:2023", geo = "KY", cpi_adj = 2023)
# View the same district with inflation-adjusted values
real_2023_data |>
filter(dist_name == "Jefferson County") |>
select(year, dist_name, rev_total, rev_total_pp)When cpi_adj is set, the returned data also includes a
cpi_adj_index column showing the multiplier applied to each
row:
When you use cpi_adj, dollar-denominated
flows are automatically adjusted for inflation:
exp_cap_total, its detailed components, and
exp_debt_interest.Variables that are NOT adjusted include:
debt_*,
fund_bal_*). These are balance-sheet levels measured at a
point in time, not annual flows. edfinr leaves them in
nominal dollars by design: debt is owed and repaid in nominal terms, and
deflating stocks alongside flows invites accidental mixing of the two.
Restating them in base-year dollars is a legitimate analysis choice; if
yours calls for it, apply the deflator yourself.cwift_est). It is a relative labor-cost index, not a
dollar amount, and is never CPI-adjusted (see the “CWIFT” article on the
package website).The flows-vs-stocks rule is easy to verify: the capital outlay flow scales, while the debt stock is identical with and without adjustment.
raw <- get_finance_data(yr = "2019", geo = "KY", dataset_type = "full")
adj <- get_finance_data(yr = "2019", geo = "KY", dataset_type = "full", cpi_adj = "2023")
# capital outlay (a flow) is scaled up to 2023 dollars
head(adj$exp_cap_total / raw$exp_cap_total, 3)
# long-term debt outstanding (a stock) is identical in both
identical(adj$debt_lt_end, raw$debt_lt_end)Every dataset includes a cpi_sy12 column that shows the
CPI index relative to the 2011-12 school year:
# Examine the CPI index values
cpi_values <- get_finance_data(yr = "all", geo = "KY") |>
select(year, cpi_sy12) |>
distinct() |>
arrange(year)
print(cpi_values)
# Calculate cumulative inflation since 2012
cpi_values |>
mutate(
inflation_since_2012 = (cpi_sy12 - 1) * 100,
inflation_label = paste0(round(inflation_since_2012, 1), "%")
)Here’s how to analyze whether education revenue has kept pace with inflation:
# get multiyear data in nominal dollars
ky_nominal <- get_finance_data(yr = "all", geo = "KY", cpi_adj = "none") |>
mutate(type = "Nominal dollars")
# get multi-year data adjusted to 2023 dollars
ky_real <- get_finance_data(yr = "all", geo = "KY", cpi_adj = "2023") |>
mutate(type = "Real 2023 dollars")
# join data
ky_data <- bind_rows(ky_nominal, ky_real)
# calculate statewide per-pupil revenue trends for real dollars
rev_trends <- ky_data |>
group_by(type, year) |>
summarize(
rev_local = sum(rev_local, na.rm = TRUE),
rev_state = sum(rev_state, na.rm = TRUE),
rev_fed = sum(rev_fed, na.rm = TRUE),
enroll = sum(enroll, na.rm = TRUE)
) |>
mutate(
rev_local_pp = rev_local / enroll,
rev_state_pp = rev_state / enroll,
rev_fed_pp = rev_fed / enroll
) |>
select(type, year, rev_local_pp:rev_fed_pp) |>
pivot_longer(
cols = rev_local_pp:rev_fed_pp,
names_to = "var", values_to = "val") |>
mutate(
var = str_remove_all(var, "rev_"),
var = str_remove_all(var, "_pp"),
var = str_to_title(var),
var = str_replace_all(var, "Fed", "Federal")
)
# plot trends
ggplot(rev_trends) +
geom_line(
aes(x = year, y = val, color = var)
) +
facet_wrap(~type) +
scale_x_continuous(breaks = seq(2013, 2023, 2)) +
scale_y_continuous(labels = scales::label_dollar()) +
labs(
title = "Comparing Nominal and Real Per-Pupil Revenue in Kentucky",
subtitle = "Statewide average per-pupil revenue by source, 2012-2023",
x = "Year",
y = "Per-Pupil Revenue",
color = "Revenue Source"
) +
theme_minimal()You can adjust to any year from 2012 to 2023. Common choices include:
# select ky district to assess
district_sample <- "Jefferson County"
# get data with nominal dollars and cpi-adjusted for different base years
nominal <- get_finance_data(yr = "2012:2023", geo = "KY") |>
filter(dist_name == district_sample) |>
select(year, rev_total_pp) |>
mutate(type = "Nominal")
adjusted_2012 <- get_finance_data(yr = "2012:2023", geo = "KY", cpi_adj = 2012) |>
filter(dist_name == district_sample) |>
select(year, rev_total_pp) |>
mutate(type = "2012 Dollars")
adjusted_2023 <- get_finance_data(yr = "2012:2023", geo = "KY", cpi_adj = 2023) |>
filter(dist_name == district_sample) |>
select(year, rev_total_pp) |>
mutate(type = "2023 Dollars")
# join and plot data
bind_rows(nominal, adjusted_2012, adjusted_2023) |>
ggplot(aes(x = year, y = rev_total_pp, color = type)) +
geom_line(linewidth = 1.2) +
scale_x_continuous(breaks = seq(2013, 2023, 2)) +
scale_y_continuous(labels = scales::label_dollar()) +
labs(
title = paste("Per-Pupil Revenue:", district_sample),
x = "Year",
y = "Revenue per Pupil",
color = "CPI Adjustment"
) +
theme_minimal()Use inflation adjustment for multi-year analyses: Comparing nominal dollars across years can be misleading.
Be consistent with your base year: Use the same
cpi_adj value for all data in an analysis.
Document your choice: Always note whether values are nominal or real, and which base year you used.
Consider your audience: Current dollars (most recent year) are often most intuitive for general audiences.
cpi_adj_index, so adjusted per-pupil values equal adjusted
totals divided by enrollment.cpi_sy12 column is always included regardless of
adjustment choice.cpi_adj baseline year falls outside the
requested yr range, that year’s file is downloaded to
source the baseline and then dropped from the returned data.get_finance_data().