1  Calculating the Fiscal Gap - Extra Decimals in tables

Code
library(tidyverse)
library(haven)
library(formatR)
library(lubridate)
library(smooth)
library(forecast)
library(scales)
library(kableExtra)
library(ggplot2)
library(readxl)
library(tidyverse)
library(data.table)
library(quantmod)
library(geofacet)
library(janitor)
library(cmapplot)

theme_set(theme_classic() )

knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)

exp_temp <- read_csv("./data/exp_temp.csv")
rev_temp <- read_csv("./data/rev_temp.csv")

1.1 Modify Expenditure File

1.1.1 Tax refunds

Aggregate expenditures: Save tax refunds as negative revenue. Code refunds to match the rev_type codes (02=income taxes, 03 = corporate income taxes, 06=sales tax, 09=motor fuel tax, 24=insurance taxes and fees, 35 = all other tax refunds).

Code
## negative revenue becomes tax refunds

tax_refund_long <- exp_temp %>%           # fund != "0401" # removes State Trust Funds
  filter(fund != "0401" & 
                              (object =="9900" |    # one-time abatements in FY23
                                 object=="9910"|object=="9921"|object=="9923"|object=="9925")) %>%
  # keeps these objects which represent revenue, insurance, treasurer,and financial and professional reg tax refunds
  mutate(refund = case_when(
                        object == "9900" ~ "FY23_Rebates",

    fund=="0278" & sequence == "00" ~ "02", # for income tax refund
    fund=="0278" & sequence == "01" ~ "03", # tax administration and enforcement and tax operations become corporate income tax refund
     fund == "0278" & sequence == "02" ~ "02",
    object=="9921" ~ "21",                # inheritance tax and estate tax refund appropriation
    object=="9923" ~ "09",                # motor fuel tax refunds
    obj_seq_type == "99250055" ~ "06",    # sales tax refund
    fund=="0378" & object=="9925" ~ "24", # insurance privilege tax refund
   (fund=="0001" & object=="9925") | (object=="9925" & fund == "0384" & fy == 2023) ~ "35", # all other taxes
   # fund=="0001" & object=="9925" ~ "35", # all other taxes
      T ~ "CHECK"))                       # if none of the items above apply to the observations, then code them as CHECK 

    
exp_temp <- left_join(exp_temp, tax_refund_long) %>%
  mutate(refund = ifelse(is.na(refund),"not refund", as.character(refund)))

tax_refund <- tax_refund_long %>% 
  group_by(refund, fy)%>%
  summarize(refund_amount = sum(expenditure, na.rm = TRUE)/1000000) %>%
  pivot_wider(names_from = refund, values_from = refund_amount, names_prefix = "ref_") %>%
  mutate_all(~replace_na(.,0)) %>%
  arrange(fy)

tax_refund %>% 
  pivot_longer(c(ref_02:ref_35, ref_FY23_Rebates), names_to = "Refund Type", values_to = "Amount") %>%
  ggplot()+
  theme_classic()+
  geom_line(aes(x=fy,y=Amount, group = `Refund Type`, color = `Refund Type`))+
  labs(title = "Refund Types", 
       caption = "Refunds are excluded from Expenditure totals and instead subtracted from Revenue totals") + 
  labs(title = "Tax refunds", 
       caption = "Rev_type codes: 02=income taxes, 03=corporate income taxes, 06=sales tax, 09=motor fuel tax, 
       24=insurance taxes and fees, 35 = all other tax refunds." )

# remove the items we recoded in tax_refund_long
# exp_temp <- exp_temp %>% filter(refund == "not refund")
Figure 1.1: Tax Refunds

Ideally the money going in and out of the funds used for refunds would be approximately equal. If equal, would drop from Fiscal Futures analysis so that Revenue reflects the amount of money the state gets to keep (and the local portion that becomes the local govt transfer).

For FY23, the one-time abatement, object 9900, is included as an expenditure item within the Department of Revenue.

Code
# manually adds the abatements as expenditure item and keeps on expenditure side.
# otherwise ignored since it is in fund 0278 and 
exp_temp <- exp_temp %>%
  mutate(in_ff = ifelse(object == 9900, 1, in_ff))

1.1.2 Pension Expenditures

State pension contributions are largely captured with object=4431. (State payments into pension fund). State payments to the following pension systems:

  • Teachers Retirement System (TRS)
  • New POB bond in 2019: Accelerated Bond Fund paid benefits in advance as lump sum
  • State Employee Retirement System (SERS)
  • State University Retirement System (SURS)
  • Judges Retirement System (JRS)
  • General Assembly Retirement System (GARS)

Modify exp_temp and move all state pension contributions to their own group (901). For more information on the variables included or excluded, please see ?sec-pensions.

Code
exp_temp <-  exp_temp %>% 
  arrange(fund) %>%
  mutate(pension = case_when( 
    (object=="4431") ~ 1, # 4431 = easy to find pension payments INTO fund
    
    # (object>"1159" & object<"1166") & fund != "0183" & fund != "0193"   ~ 2, 
    # objects 1159 to 1166 are all considered Retirement by Comptroller, 
    # Excluded - employer contributions from agencies/organizations/etc.
    
    (object=="1298" &  # Purchase of Investments, Normally excluded
       (fy==2010 | fy==2011) & 
       (fund=="0477" | fund=="0479" | fund=="0481")) ~ 3, #judges retirement OUT of fund
    # state borrowed money from pension funds to pay for core services during 2010 and 2011. 
    # used to fill budget gap and push problems to the future. 
    
    fund == "0319" ~ 4, # pension stabilization fund
    TRUE ~ 0) )

table(exp_temp$pension) 

     0      1      3      4 
237827    242      6     15 
Code
exp_temp %>% 
  filter(pension != 0) %>%
  mutate(pension = as.factor(pension))%>%
  group_by(fy, pension) %>% 
  summarize(expenditure = sum(expenditure, na.rm = TRUE)) %>%
  ggplot(aes(x=fy, y = expenditure, group=pension)) + 
  theme_classic()+
  geom_col(aes(fill = pension)) + 

  labs (title = "Pension expenditures", 
  caption = "1 = State contributions INTO pension funds. 
  3 = Purchase of Investments anomoly in 2010 and 2011. 
  4 = pension stabilization fund")+
    theme(legend.position = "bottom")
Figure 1.2: Pensions
Code
# special accounting of pension obligation bond (POB)-funded contributions to JRS, SERS, GARS, TRS 

exp_temp <- exp_temp %>% 
  # change object for 2010 and 2011, retirement expenditures were bond proceeds and would have been excluded
  mutate(object = ifelse((pension >0 & in_ff == "0"), "4431", object)) %>% 
  # changes weird teacher & judge retirement system  pensions object to normal pension object 4431
  mutate(pension =  ifelse(pension >0 & in_ff == "0", 6, pension)) %>% # coded as 6 if it was supposed to be excluded. 
  mutate(in_ff = ifelse(pension>0, "1", in_ff))

table(exp_temp$pension) 

     0      1      4      6 
237827    240     15      8 
Code
# all other pensions objects  codes get agency code 901 for State Pension Contributions
exp_temp <- exp_temp %>% 
  mutate(agency = ifelse(pension>0, "901", as.character(agency)),
         agency_name = ifelse(agency == "901", "State Pension Contributions", as.character(agency_name)))

exp_temp %>% 
 filter(pension > 0) %>%  
  mutate(pension = as.factor(pension)) %>%
  group_by(fy, pension) %>% 
  summarize(expenditure = sum(expenditure, na.rm=TRUE)) %>%
  ggplot(aes(x=fy, y=expenditure, color = pension)) +
  geom_line() + 
  theme_classic()+
  labs (title = "Pension Expenditures", 
  caption = "")

exp_temp %>% 
 filter(pension > 0) %>%  
  group_by(fy) %>% 
  summarize(expenditure = sum(expenditure, na.rm=TRUE)) %>%
  ggplot(aes(x=fy, y=expenditure)) +
  geom_line() + 
  theme_classic()+
  labs (title = "Pension Expenditures")
Figure 1.3: Pension Expenditures
Figure 1.4: Pension Expenditures

1.1.3 Drop Interfund transfers

Drop all cash transfers between funds, statutory transfers, and purchases of investments from expenditure data.

  • object == 1993 is for interfund cash transfers
  • agency == 799 is for statutory transfers
  • object == 1298 is for purchase of investments and is not spending EXCEPT for costs in 2010 and 2011 (and were recoded already to object == “4431”). Over 168,000 observations remain.
    • 153,889 observations on 1/23/2022?
Code
transfers_drop <- exp_temp %>% filter(
  agency == "799" | # statutory transfers
           object == "1993" |  # interfund cash transfers
           object == "1298") # purchase of investments
transfers_drop # items being dropped, 
Code
# always check to make sure you aren't accidently dropping something of interest.

exp_temp <- anti_join(exp_temp, transfers_drop)
exp_temp

1.1.4 State employee healthcare costs

Coding healthcare costs was quite difficult. Over the years, State employee healthcare has been within Central Management Bureau of Benefits and Healthcare & Family Services.

If observation is a group insurance contribution, then the expenditure amount is set to $0 (essentially dropped from analysis).

Agency 416 had group insurance contributions for 1998-2005 and 2013-present. Agency 478 had group insurance contributions from 2006-2012.

FY2021 and FY2022 contributions coded with object = 1900 (lump sum) for some reason??

Code
#if observation is a group insurance contribution, then the expenditure amount is set to $0 (essentially dropped from analysis)

# pretend eehc is named group_insurance_contribution or something like that
# eehc coded as zero implies that it is group insurance
# if eehc=0, then expenditures are coded as zero for group insurance to avoid double counting costs


exp_temp <- exp_temp %>% 
  mutate(eehc = ifelse(
    # group insurance contributions for 1998-2005 and 2013-present
   fund == "0001" & (object == "1180" | object =="1900") & agency == "416" & appr_org=="20", 0, 1) )%>% 
  
  mutate(eehc = ifelse(
    # group insurance contributions for 2006-2012
    fund == "0001" & object == "1180" & agency == "478" 
    & appr_org=="80", 0, eehc) )%>%
    
   # group insurance contributions from road fund
  # coded with 1900 for some reason??
    mutate(eehc = ifelse(
      fund == "0011" & object == "1900" & 
        agency == "416" & appr_org=="20", 0, eehc) ) %>%
  
  mutate(expenditure = ifelse(eehc=="0", 0, expenditure)) %>%
  
  mutate(agency = case_when(   # turns specific items into State Employee Healthcare (agency=904)
      fund=="0907" & (agency=="416" & appr_org=="20") ~ "904",   # central management Bureau of benefits using health insurance reserve 
      fund=="0907" & (agency=="478" & appr_org=="80") ~ "904",   # agency = 478: healthcare & family services using health insurance reserve - stopped using this in 2012
      TRUE ~ as.character(agency))) %>%
  mutate(agency_name = ifelse(
    agency == "904", "STATE EMPLOYEE HEALTHCARE", as.character(agency_name)),
         in_ff = ifelse( agency == "904", 1, in_ff),
         group = ifelse(agency == "904", "904", as.character(agency)))  
# creates group variable

# Default group = agency number

healthcare_costs <- exp_temp %>% filter(group == "904")

healthcare_costs
Code
exp_temp %>% 
  filter(group == "904") %>% 
  group_by(fy) %>% 
  summarise(healthcare_cost = sum(expenditure, na.rm = TRUE)) %>% 
  ggplot() +
  geom_line(aes(x=fy, y=healthcare_cost)) + 
  labs(title="State Employee Healthcare Costs - Included in Fiscal Futures Model", 
       caption = "Fund 0907 for agencies responsible for health insurance reserve (DHFS & CMS)")

Code
#exp_temp <- anti_join(exp_temp, healthcare_costs) %>% mutate(expenditure = ifelse(object == "1180", 0, expenditure))

#healthcare_costs_yearly <- healthcare_costs %>% group_by(fy, group) %>% summarise(healthcare_cost = sum(expenditure, na.rm = TRUE)/1000000) %>% select(-group)

1.1.5 Local Transfers

Separate transfers to local from parent agencies that come from DOR(492) or Transportation (494). Treats muni revenue transfers as expenditures, not negative revenue.

The share of certain taxes levied state-wide at a common rate and then transferred to local governments. (Purely local-option taxes levied by specific local governments with the state acting as collection agent are NOT included.)

The six corresponding revenue items are:

  • Local share of Personal Income Tax
    • Individual Income Tax Pass-Through New 2021 (source 2582).
  • Local share of General Sales Tax
  • Personal Property Replacement Tax on Business Income
  • Personal Property Replacement Tax on Public Utilities
  • Local share of Motor Fuel Tax
  • Transportation Renewal Fund 0952

Until Dec 18. 2022, Local CURE was being aggregated into Revenue totals since the agency was the Department of Revenue. However the $371 million expenditure is for “LOC GOVT ARPA” and the revenue source that is Local CURE is also $371 million. Since it cancels out and is just passed through the state government, I am changing changing the fund_ab_in file so that in_ff=0 for the Local CURE fund. It also inflates the department of revenue expenditures in a misleading way when the expense is actually a transfer to local governments.

  • Dropping Local CURE fund from analysis results in a $371 million decrease in the department of Revenue (where the Local Government ARPA transfer money). The appropriation for it was over $740 million so some will probably be rolled over to FY23 too.
  • In the FY21 New and Reused Funds word document, 0325 Local CURE is described as “Created as a federal trust fund. The fund is established to receive transfers from either the disaster response and recovery fund or the state cure fund of federal funds received by the state. These transfers, subject to appropriation, will provide for the administration and payment of grants and expense reimbursements to units of local government. Revenues should be under Federal Other and expenditures under Commerce and Economic Opportunity.” - I propose changing it to exclude for both.
Code
exp_temp <- exp_temp %>% mutate(
  agency = case_when(fund=="0515" & object=="4470" & type=="08" ~ "971", # income tax to local governments
                     fund=="0515" & object=="4491" & type=="08" & sequence=="00" ~ "971", # object is shared revenue payments
                     fund=="0802" & object=="4491" ~ "972", #pprt transfer
                     fund=="0515" & object=="4491" & type=="08" & sequence=="01" ~ "976", #gst to local
                     fund=="0627" & object=="4472"~ "976" , # public transportation fund but no observations exist
                     fund=="0648" & object=="4472" ~ "976", # downstate public transportation, but doesn't exist
                     fund=="0515" & object=="4470" & type=="00" ~ "976", # object 4470 is grants to local governments
                    object=="4491" & (fund=="0188"|fund=="0189") ~ "976",
                     fund=="0187" & object=="4470" ~ "976",
                     fund=="0186" & object=="4470" ~ "976",
                    object=="4491" & (fund=="0413"|fund=="0414"|fund=="0415")  ~ "975", #mft to local
                  fund == "0952"~ "975", # Added Sept 29 2022 AWM. Transportation Renewal MFT
                    TRUE ~ as.character(agency)),
  
  agency_name = case_when(agency == "971"~ "INCOME TAX 1/10 TO LOCAL",
                          agency == "972" ~ "PPRT TRANSFER TO LOCAL",
                          agency == "975" ~ "MFT TO LOCAL",
                          agency == "976" ~ "GST TO LOCAL",
                          TRUE~as.character(agency_name)),
  group = ifelse(agency>"970" & agency < "977", as.character(agency), as.character(group)))
Code
transfers_long <- exp_temp %>% 
  filter(group == "971" |group == "972" | group == "975" | group == "976")

transfers_long %>% 
  group_by(agency_name, group, fy) %>% 
  summarize(expenditure = sum(expenditure, na.rm=TRUE) )%>% 
  ggplot() + 
  geom_line(aes(x=fy, y = expenditure, color=agency_name)) + 
  theme_classic()+
  theme(legend.position = "bottom", legend.title=element_blank())+
  labs(title = "Transfers to Local Governments", 
       caption = "Data Source: Illinois Office of the Comptroller")

transfers <- transfers_long %>%
  group_by(fy, group ) %>%
  summarize(sum_expenditure = sum(expenditure)/1000000) %>%
  pivot_wider(names_from = "group", values_from = "sum_expenditure", names_prefix = "exp_" )

exp_temp <- anti_join(exp_temp, transfers_long)


dropped_inff_0 <- exp_temp %>% filter(in_ff == 0)

exp_temp <- exp_temp %>% filter(in_ff == 1) # drops in_ff = 0 funds AFTER dealing with net-revenue above
Figure 1.5: Drop Transfers from State to Local Governments

The Local Transfers from the Personal Property Replacement Tax (fund 802) increased over $2 billion from corporate income taxes alone. Personal property replacement taxes (PPRT) are revenues collected by the state of Illinois and paid to local governments to replace money that was lost by local governments when their powers to impose personal property taxes on corporations, partnerships, and other business entities were taken away.

1.1.6 Debt Service

Debt Service expenditures include interest payment on both short-term and long-term debt. We do not include escrow or principal payments.

Decision from Sept 30 2022: We are no longer including short term principal payments as a cost; only interest on borrowing is a cost. Pre FY22 and the FY21 correction, we did include an escrow payment and principle payments as costs but not bond proceeds as revenues. This caused expenditures to be inflated because we were essentially counting debt twice - the principle payment and whatever the money was spent on in other expenditure categories, which was incorrect.

Code
debt_drop <- exp_temp %>% 
  filter(object == "8841" |  object == "8811")  
# escrow  OR  principle

#debt_drop %>% group_by(fy) %>% summarize(sum = sum(expenditure)) %>% arrange(-fy)


debt_keep <- exp_temp %>% 
  filter(fund != "0455" & (object == "8813" | object == "8800" )) 
# examine the debt costs we want to include

#debt_keep %>% group_by(fy) %>% summarize(sum = sum(expenditure)) %>% arrange(-fy) 


exp_temp <- anti_join(exp_temp, debt_drop) 
exp_temp <- anti_join(exp_temp, debt_keep)

debt_keep <- debt_keep %>%
  mutate(
    agency = ifelse(fund != "0455" & (object == "8813" | object == "8800"), "903", as.character(agency)),
    group = ifelse(fund != "0455" & (object == "8813" | object == "8800"), "903", as.character(group)),
    in_ff = ifelse(group == "903", 1, as.character(in_ff)))

debt_keep_yearly <- debt_keep %>% group_by(fy, group) %>% summarize(debt_cost = sum(expenditure,na.rm=TRUE)/1000000) %>% select(-group)

1.1.7 Medicaid

Medicaid. That portion of the Healthcare and Family Services (or Public Aid in earlier years, agency code 478) budget for Medical (appr_organization code 65) for awards and grants (object codes 4400 and 4900).

State CURE will remain in the Medicaid expenditure category due to the nature of it being federal funds providing public health services and funding to locations that provide public services.

  • Uses same appropriation name of “HEALTHCARE PROVIDER RELIEF” and fund == 0793 and obj_seq_type == 49000000. So can defend the “mistake” of including healthcare provider relief as Medicaid expenditure.

1.1.8 Add Other Fiscal Future group codes

Code
exp_temp <- exp_temp %>%
  #mutate(agency = as.numeric(agency) ) %>%
  # arrange(agency)%>%
  mutate(
    group = case_when(
      agency>"100"& agency<"200" ~ "910", # legislative
      
      agency == "528"  | (agency>"200" & agency<"300") ~ "920", # judicial
      pension>0  ~ "901", # pensions
      (agency>"309" & agency<"400") ~ "930",    # elected officers
      
      agency == "586" ~ "959", # create new K-12 group

      agency=="402" | agency=="418" | agency=="478" | agency=="444" | agency=="482" ~ as.character(agency), # aging, CFS, HFS, human services, public health
      T ~ as.character(group))
    ) %>%      

  
  mutate(group = case_when(
    agency=="478" & (appr_org=="01" | appr_org == "65" | appr_org=="88") & (object=="4900" | object=="4400") ~ "945", # separates CHIP from health and human services and saves it as Medicaid
    
    agency == "586" & fund == "0355" ~ "945",  # 586 (Board of Edu) has special education which is part of medicaid
    
    # OLD CODE: agency == "586" & appr_org == "18" ~ "945", # Spec. Edu Medicaid Matching
    
    agency=="425" | agency=="466" | agency=="546" | agency=="569" | agency=="578" | agency=="583" | agency=="591" | agency=="592" | agency=="493" | agency=="588" ~ "941", # public safety & Corrections
    
    agency=="420" | agency=="494" |  agency=="406" | agency=="557" ~ as.character(agency), # econ devt & infra, tollway
    
    agency=="511" | agency=="554" | agency=="574" | agency=="598" ~ "946",  # Capital improvement
    
    agency=="422" | agency=="532" ~ as.character(agency), # environment & nat. resources
    
    agency=="440" | agency=="446" | agency=="524" | agency=="563"  ~ "944", # business regulation
    
    agency=="492" ~ "492", # revenue
    
    agency == "416" ~ "416", # central management services
    agency=="448" & fy > 2016 ~ "416", #add DoIT to central management 
    
    T ~ as.character(group))) %>%
  
  
  mutate(group = case_when(
    # agency=="684" | agency=="691"  ~ as.character(agency), # moved under higher education in next line. 11/28/2022 AWM
    
    agency=="692" | agency=="695" | agency == "684" |agency == "691" | (agency>"599" & agency<"677") ~ "960", # higher education
    
    agency=="427"  ~ as.character(agency), # employment security
    
    agency=="507"|  agency=="442" | agency=="445" | agency=="452" |agency=="458" | agency=="497" ~ "948", # other departments
    
    # other boards & Commissions
    agency=="503" | agency=="509" | agency=="510" | agency=="565" |agency=="517" | agency=="525" | agency=="526" | agency=="529" | agency=="537" | agency=="541" | agency=="542" | agency=="548" |  agency=="555" | agency=="558" | agency=="559" | agency=="562" | agency=="564" | agency=="568" | agency=="579" | agency=="580" | agency=="587" | agency=="590" | agency=="527" | agency=="585" | agency=="567" | agency=="571" | agency=="575" | agency=="540" | agency=="576" | agency=="564" | agency=="534" | agency=="520" | agency=="506" | agency == "533" ~ "949", 
    
    # non-pension expenditures of retirement funds moved to "Other Departments"
    # should have removed pension expenditures already from exp_temp in Pensions step above
    agency=="131" | agency=="275" | agency=="589" |agency=="593"|agency=="594"|agency=="693" ~ "948",
    
    T ~ as.character(group))) %>%

  mutate(group_name = 
           case_when(
             group == "416" ~ "Central Management",
             group == "478" ~ "Healthcare and Family Services",
             group == "482" ~ "Public Health",
             group == "900" ~ "NOT IN FRAME",
             group == "901" ~ "STATE PENSION CONTRIBUTION",
             group == "903" ~ "DEBT SERVICE",
             group == "910" ~ "LEGISLATIVE"  ,
             group == "920" ~ "JUDICIAL" ,
             group == "930" ~ "ELECTED OFFICERS" , 
             group == "940" ~ "OTHER HEALTH-RELATED", 
             group == "941" ~ "PUBLIC SAFETY" ,
             group == "942" ~ "ECON DEVT & INFRASTRUCTURE" ,
             group == "943" ~ "CENTRAL SERVICES",
             group == "944" ~ "BUS & PROFESSION REGULATION" ,
             group == "945" ~ "MEDICAID" ,
             group == "946" ~ "CAPITAL IMPROVEMENT" , 
             group == "948" ~ "OTHER DEPARTMENTS" ,
             group == "949" ~ "OTHER BOARDS & COMMISSIONS" ,
             group == "959" ~ "K-12 EDUCATION" ,
             group == "960" ~ "UNIVERSITY EDUCATION" ,
             group == agency ~ as.character(group),
             TRUE ~ "Check name"),
         year = fy)

exp_temp %>% filter(group_name == "Check name")
Code
#write_csv(exp_temp, "all_expenditures_recoded.csv")
Important

All expenditures recoded but not aggregated: Allows for inspection of individual expenditures within larger categories. This stage of the data is extremely useful for investigating how individual items have been coded before they are aggregated into larger categories.

1.2 Modify Revenue data

Revenue Categories NOT included in Fiscal Futures:
- 32. Garnishment-Levies. (State is fiduciary, not beneficiary.)
- 45. Student Fees-Universities. (Excluded from state-level budget.)
- 51. Retirement Contributions (of individuals and non-state entities).
- 66. Proceeds, Investment Maturities. (Not sustainable flow.)
- 72. Bond Issue Proceeds. (Not sustainable flow.)
- 75. Inter-Agency Receipts.
- 79. Cook County Intergovernmental Transfers. (State is not beneficiary.)
- 98. Prior Year Refunds.
- 99. Statutory Transfers.

All Other Sources

Expanded to include the following smaller sources:
- 30. Horse Racing Taxes & Fees.
- 60. Other Grants and Contracts.
- 63. Investment Income.

For aggregating revenue, use the rev_1998_2022 dataframe, join the funds_ab_in_2022 file to it, and then join the ioc_source_type file to the dataset. Remember: You need to update the funds_ab_in and ioc_source_type file every year!

Code
# recodes old agency numbers to consistent agency number
rev_temp <- rev_temp %>% 
  mutate(agency = case_when(
    (agency=="438"| agency=="475" |agency == "505") ~ "440",
    # financial institution &  professional regulation &
     # banks and real estate  --> coded as  financial and professional reg
    agency == "473" ~ "588", # nuclear safety moved into IEMA
    (agency =="531" | agency =="577") ~ "532", # coded as EPA
    (agency =="556" | agency == "538") ~ "406", # coded as agriculture
    agency == "560" ~ "592", # IL finance authority (fire trucks and agriculture stuff)to state fire marshal
    agency == "570" & fund == "0011" ~ "494",   # city of Chicago road fund to transportation
    TRUE ~ (as.character(agency)))) 

1.2.1 Federal to State Transfers

For an deeper look at federal revenue to Illinois, ?sec-covid-federal-funds.

Code
#rev_temp <- rev_temp %>% filter(in_ff==1)

rev_temp <- rev_temp %>% 
  mutate(
    rev_type = ifelse(rev_type=="57" & agency=="478" & (source=="0618"|source=="2364"|source=="0660"|source=="1552"| source=="2306"| source=="2076"|source=="0676"|source=="0692"), "58", rev_type),
    rev_type_name = ifelse(rev_type=="58", "Federal Medicaid Reimbursements", rev_type_name),
    rev_type = ifelse(rev_type=="57" & agency=="494", "59", rev_type),
    rev_type_name = ifelse(rev_type=="59", "Federal Transportation", rev_type_name),
    rev_type_name = ifelse(rev_type=="57", "Federal - Other", rev_type_name),
    rev_type = ifelse(rev_type=="6", "06", rev_type),
    rev_type = ifelse(rev_type=="9", "09", rev_type)) 

rev_temp %>% 
  filter(rev_type == "58" | rev_type == "59" | rev_type == "57") %>% 
  group_by(fy, rev_type, rev_type_name) %>% 
  summarise(receipts = sum(receipts, na.rm = TRUE)/1000000) %>% 
  ggplot() +
      geom_recessions(xformay = "numeric",text = FALSE)+
  geom_line(aes(x=fy, y=receipts,color=rev_type_name)) +
      theme_classic() +
  scale_y_continuous(labels = comma)+
  labs(title = "Federal to State Transfers", 
       y = "Millions of Dollars", x = "") + 
  theme(legend.position = "bottom", legend.title = element_blank()  )
Figure 1.6

Dropping State CURE Revenue

The Fiscal Futures model focuses on sustainable revenue sources. To understand our fiscal gap and outlook, we need to exclude these one time revenues. GOMB has emphasized that they have allocated COVID dollars to one time expenditures (unemployment trust fund, budget stabilization fund, etc.). The fiscal gap, graphs,and CAGRs have been recalculated in the [Drop COVID Dollars] section below.

NOTE: The code chunk below only drops revenue sources with the source name of “Federal Stimulus Package” (which is the State and Local CURE revenue). Additional federal money went into other funds during the beginning of pandemic. Many departments saw increased grants and received other funds (e.g. funds)

Code
rev_temp <- rev_temp %>% mutate(covid_dollars = ifelse(source_name_AWM == "FEDERAL STIMULUS PACKAGE",1,0))

rev_temp %>% filter(source_name_AWM == "FEDERAL STIMULUS PACKAGE") %>%
  group_by(fy) %>% summarize(Received = sum(receipts))

1.2.2 Health Insurance Premiums from Employees

Insurance premiums for employees is coded below but it is NOT used in the fiscal futures model. Employee and employer premiums are considered rev_51 and dropped from analysis in later step.

  • 0120 = ins prem-option life
  • 0120 = ins prem-optional life/univ
  • 0347 = optional health - HMO
  • 0348 = optional health - dental
  • 0349 = optional health - univ/local SI
  • 0350 = optional health - univ/local
  • 0351 = optional health - retirement
  • 0352 = optional health - retirement SI
  • 0353 = optional health - retire/dental
  • 0354 = optional health - retirement hmo
  • 2199-2209 = various HMOs, dental, health plans from Health Insurance Reserve (fund)
Code
#collect optional insurance premiums to fund 0907 for use in eehc expenditure  
rev_temp <- rev_temp %>% 
  mutate(
    #variable not used in aggregates, but could be interesting for other purposes
    employee_premiums = ifelse(fund=="0907" & (source=="0120"| source=="0121"| (source>"0345" & source<"0357")|(source>"2199" & source<"2209")), 1, 0),
    
    # adds more rev_type codes
    rev_type = case_when(
      fund =="0427" ~ "12", # pub utility tax
      fund == "0742" | fund == "0473" ~ "24", # insurance and fees
      fund == "0976" ~ "36",# receipts from rev producing
      fund == "0392" |fund == "0723" ~ "39", # licenses and fees
      fund == "0656" ~ "78", #all other rev sources
      TRUE ~ as.character(rev_type)))
# if not mentioned, then rev_type as it was



# # optional insurance premiums = employee insurance premiums

# emp_premium <- rev_temp %>%
#   group_by(fy, employee_premiums) %>%
#   summarize(employee_premiums_sum = sum(receipts)/1000000) %>%
#   filter(employee_premiums == 1) %>%
#   rename(year = fy) %>% 
#   select(-employee_premiums)

emp_premium_long <- rev_temp %>%  filter(employee_premiums == 1)
# 381 observations have employee premiums == 1


# drops employee premiums from revenue
# rev_temp <- rev_temp %>% filter(employee_premiums != 1)
# should be dropped in next step since rev_type = 51

Note: In FY21, employee premiums were subtracted from state healthcare costs on the expenditure side to calculate a “Net Healthcare Cost” but that methodology has been discontinued. Totals were practically unchanged: revenue from employee premiums is also very small.

1.2.3 Transfers in and Out:

Funds that hold and disperse local taxes or fees are dropped from the analysis. Then other excluded revenue types are also dropped.

Drops Blank, Student Fees, Retirement contributions, proceeds/investments, bond issue proceeds, interagency receipts, cook IGT, Prior year refunds:

Code
rev_temp <- rev_temp %>% 
  filter(in_ff == 1) %>% 
  mutate(local = ifelse(is.na(local), 0, local)) %>% # drops all revenue observations that were coded as "local == 1"
  filter(local != 1)

# 1175 doesnt exist?
in_from_out <- c("0847", "0867", "1175", "1176", "1177", "1178", "1181", "1182", "1582", "1592", "1745", "1982", "2174", "2264")

# what does this actually include:
# all are items with rev_type = 75 originally. 
in_out_df <- rev_temp %>%
  mutate(infromout = ifelse(source %in% in_from_out, 1, 0)) %>%
  filter(infromout == 1)

rev_temp <- rev_temp %>% 
  mutate(rev_type_new = ifelse(source %in% in_from_out, "76", rev_type))
# if source contains any of the codes in in_from_out, code them as 76 (all other rev).
# I end up excluding rev_76 in later steps
Code
# revenue types to drop
drop_type <- c("32", "45", "51", 
               "66", "72", "75", "76", "79", "98", "99")

# drops Blank, Student Fees, Retirement contributions, proceeds/investments,
# bond issue proceeds, interagency receipts, cook IGT, Prior year refunds.


rev_temp <- rev_temp %>% filter(!rev_type_new %in% drop_type)
# keep observations that do not have a revenue type mentioned in drop_type

table(rev_temp$rev_type_new)

   02    03    06    09    12    15    18    21    24    27    30    31    33 
  177   132   852   134   590   269    47  1421   473    79   665   130   141 
   35    36    39    42    48    54    57    58    59    60    63    78 
  691  5328  9487  2939    32  1283  6679   646   233   105  5321 11712 
Code
rev_temp %>% 
  group_by(fy, rev_type_new) %>% 
  summarize(total_reciepts = sum(receipts)/1000000) %>%
  pivot_wider(names_from = rev_type_new, values_from = total_reciepts, names_prefix = "rev_") 
Code
# combines smallest 4  categories to to "Other"
# they were the 4 smallest in past years, are they still the 4 smallest? 

rev_temp <- rev_temp %>%  
 mutate(rev_type_new = ifelse(rev_type=="30" | rev_type=="60" | rev_type=="63",  ##  | rev_type=="76", # Added 76 to drop_types
                              "78", rev_type_new))


#table(rev_temp$rev_type_new)  # check work



rm(rev_1998_2022)
rm(exp_1998_2022)


#write.csv(exp_temp, "exp_fy22_recoded_22March2024.csv")
#write.csv(rev_temp, "rev_fy22_recoded_22March2024.csv")

1.3 Pivoting and Merging

  • Local Government Transfers (exp_970) should be on the expenditure side

1.3.1 Revenues

Code
ff_rev <- rev_temp %>% 
  group_by(rev_type_new, fy) %>% 
  summarize(sum_receipts = sum(receipts, na.rm=TRUE)/1000000 ) %>%
  pivot_wider(names_from = "rev_type_new", values_from = "sum_receipts", names_prefix = "rev_")

# ff_rev<- left_join(ff_rev, tax_refund)

#ff_rev <- left_join(ff_rev, pension2_fy22, by=c("fy" = "year"))

#ff_rev <- left_join(ff_rev, eehc2_amt) 
ff_rev <- mutate_all(ff_rev, ~replace_na(.,0))

# 
# ff_rev <- ff_rev %>%
#   mutate(rev_02 = rev_02 - ref_02,
#          rev_03 = rev_03 - ref_03,
#          rev_06 = rev_06 - ref_06,
#          rev_09 = rev_09 - ref_09,
#          rev_21 = rev_21 - ref_21,
#          rev_24 = rev_24 - ref_24,
#          rev_35 = rev_35 - ref_35
# 
#       #   rev_78new = rev_78 #+ pension_amt #+ eehc
#          ) %>% 
#   select(-c(ref_02:ref_35, rev_99, rev_NA, rev_76
#             #, ref_CHECK#, pension_amt , rev_76,
#           #  , eehc
#             ))
# 
# ff_rev



#noproblem <- c(0)  # if ref_CHECK = $0, then there is no problem. :) 

# if((sum(ff_rev$ref_CHECK) == 0 )){
# 
# ff_rev <- ff_rev %>%
#   
#   mutate(rev_02 = rev_02 - ref_02,
#          rev_03 = rev_03 - ref_03,
#          rev_06 = rev_06 - ref_06,
#          rev_09 = rev_09 - ref_09,
#          rev_21 = rev_21 - ref_21,
#          rev_24 = rev_24 - ref_24,
#          rev_35 = rev_35 - ref_35
#          ) %>% 
#   select(-c(ref_02:ref_35, rev_99, rev_76, ref_CHECK )) 
# }else{"You have a problem! Check what revenue items did not have rev codes (causing it to be coded as rev_NA) or the check if there were refunds that were not assigned revenue codes (tax_refunds_long objects)"}

ff_rev %>%  mutate_all(., ~round(.,digits=0))
Table 1.1: Pivoted Revenue Table ($ Millions) - Intermediate Step

Since I already pivot_wider()ed the table in the previous code chunk, I now change each column’s name by using rename() to set new variable names. Ideally the final dataframe would have both the variable name and the variable label but I have not done that yet.

Code
aggregate_rev_labels <- ff_rev %>%
  rename("INDIVIDUAL INCOME TAXES, gross of local, net of refunds" = rev_02,
         "CORPORATE INCOME TAXES, gross of PPRT, net of refunds" = rev_03,
         "SALES TAXES, gross of local share" = rev_06 ,
         "MOTOR FUEL TAX, gross of local share, net of refunds" = rev_09 ,
         "PUBLIC UTILITY TAXES, gross of PPRT" = rev_12,
         "CIGARETTE TAXES" = rev_15 ,
         "LIQUOR GALLONAGE TAXES" = rev_18,
         "INHERITANCE TAX" = rev_21,
         "INSURANCE TAXES&FEES&LICENSES, net of refunds" = rev_24 ,
         "CORP FRANCHISE TAXES & FEES" = rev_27,
         # "HORSE RACING TAXES & FEES" = rev_30,  # in Other
         "MEDICAL PROVIDER ASSESSMENTS" = rev_31 ,
         # "GARNISHMENT-LEVIES " = rev_32 , # dropped
         "LOTTERY RECEIPTS" = rev_33 ,
         "OTHER TAXES" = rev_35,
         "RECEIPTS FROM REVENUE PRODUCNG" = rev_36, 
         "LICENSES, FEES & REGISTRATIONS" = rev_39 ,
         "MOTOR VEHICLE AND OPERATORS" = rev_42 ,
         # "STUDENT FEES-UNIVERSITIES" = rev_45,   # dropped
         "RIVERBOAT WAGERING TAXES" = rev_48 ,
         # "RETIREMENT CONTRIBUTIONS " = rev_51, # dropped
         "GIFTS AND BEQUESTS" = rev_54, 
         "FEDERAL OTHER" = rev_57 ,
         "FEDERAL MEDICAID" = rev_58, 
         "FEDERAL TRANSPORTATION" = rev_59 ,
         # "OTHER GRANTS AND CONTRACTS" = rev_60, #other
         # "INVESTMENT INCOME" = rev_63, # other
         # "PROCEEDS,INVESTMENT MATURITIES" = rev_66 , #dropped
         # "BOND ISSUE PROCEEDS" = rev_72,  #dropped
         # "INTER-AGENCY RECEIPTS" = rev_75,  #dropped
         # "TRANSFER IN FROM OUT FUNDS" = rev_76,  #other
         "ALL OTHER SOURCES" = rev_78,
         # "COOK COUNTY IGT" = rev_79, #dropped
         # "PRIOR YEAR REFUNDS" = rev_98 #dropped
  ) 

aggregate_rev_labels %>% mutate_all(., ~round(., digits = 0))
Table 1.2: Aggregated Revenue Categories ($ Millions), with old labels

1.3.2 Expenditures

Create exp_970 for all local government transfers (exp_971 + exp_972 + exp_975 + exp_976).

Code
ff_exp <- exp_temp %>% 
  group_by(fy, group) %>% 
  summarize(sum_expenditures = sum(expenditure, na.rm=TRUE)/1000000 ) %>%
  pivot_wider(names_from = "group", values_from = "sum_expenditures", names_prefix = "exp_")%>%
  
    left_join(debt_keep_yearly) %>%
  mutate(exp_903 = debt_cost) %>%

  #  left_join(healthcare_costs_yearly) %>%

  # join state employee healthcare and subtract employee premiums
  # left_join(emp_premium, by = c("fy" = "year")) %>%
#  mutate(exp_904_new = (`healthcare_cost` - `employee_premiums_sum`)) %>% # state employee healthcare premiums
  
 # left_join(retirement_contributions) %>%
  #    mutate(exp_901_new = exp_901 - contributions/1000000) %>% #employee pension contributions


  # join local transfers and create exp_970
  left_join(transfers) %>%
  mutate(exp_970 = exp_971 + exp_972  + exp_975 + exp_976)

ff_exp<- ff_exp %>% 
  select(-c(debt_cost, exp_971:exp_976)) # drop unwanted columns

ff_exp # not labeled
Table 1.3: Pivoted Expenditure Categories ($ Millions)
Code
# exp_temp %>%
#   group_by(year, group) %>%
#   summarize(sum_expenditure = round(sum(expenditure)/1000000)) %>%
#   arrange(year) %>%
#   pivot_wider(names_from = "group", values_from = "sum_expenditure")
# 

aggregate_exp_labeled <- exp_temp %>%
  group_by(year, group_name) %>%
  summarize(sum_expenditure = sum(expenditure)/1000000) %>%
  arrange(year) %>%
  pivot_wider(names_from = "group_name", values_from = "sum_expenditure")

aggregate_exp_labeled %>% mutate_all(., ~round(., digits = 0))
Table 1.4: Final Expenditure Categories, with Fiscal Futures Grouped Expenditure Categories

2 Graphs and Tables

Create total revenues and total expenditures only:

  • after aggregating expenditures and revenues, pivoting wider, then I want to drop the columns that I no longer want and then pivot_longer(). After pivoting_longer() and creating rev_long and exp_long, expenditures and revenues are in the same format and can be combined together for the totals and gap each year.
Code
rev_long <- pivot_longer(ff_rev, rev_02:rev_78, names_to = c("type","Category"), values_to = "Dollars", names_sep = "_") %>% 
  rename(Year = fy) %>%
  mutate(Category_name = case_when(
    Category == "02" ~ "INDIVIDUAL INCOME TAXES" ,
    Category == "03" ~ "CORPORATE INCOME TAXES" ,
    Category == "06" ~ "SALES TAXES" ,
    Category == "09" ~ "MOTOR FUEL TAX" ,
    Category == "12" ~ "PUBLIC UTILITY TAXES" ,
    Category == "15" ~ "CIGARETTE TAXES" ,
    Category == "18" ~ "LIQUOR GALLONAGE TAXES" ,
    Category == "21" ~ "INHERITANCE TAX" ,
    Category == "24" ~ "INSURANCE TAXES&FEES&LICENSES" ,
    Category == "27" ~ "CORP FRANCHISE TAXES & FEES" ,
    Category == "30" ~ "HORSE RACING TAXES & FEES",  # in Other
    Category == "31" ~ "MEDICAL PROVIDER ASSESSMENTS" ,
    Category == "32" ~ "GARNISHMENT-LEVIES" , # dropped
    Category == "33" ~  "LOTTERY RECEIPTS" ,
    Category == "35" ~  "OTHER TAXES" ,
    Category == "36" ~  "RECEIPTS FROM REVENUE PRODUCING", 
    Category == "39" ~  "LICENSES, FEES & REGISTRATIONS" ,
    Category == "42" ~  "MOTOR VEHICLE AND OPERATORS" ,
    Category == "45" ~  "STUDENT FEES-UNIVERSITIES",   # dropped
    Category == "48" ~  "RIVERBOAT WAGERING TAXES" ,
    Category == "51" ~  "RETIREMENT CONTRIBUTIONS" , # dropped
    Category == "54" ~ "GIFTS AND BEQUESTS", 
    Category == "57" ~  "FEDERAL OTHER" ,
    Category == "58" ~  "FEDERAL MEDICAID", 
    Category == "59" ~  "FEDERAL TRANSPORTATION" ,
    Category == "60" ~  "OTHER GRANTS AND CONTRACTS", #other
    Category == "63" ~  "INVESTMENT INCOME", # other
    Category == "66" ~ "PROCEEDS,INVESTMENT MATURITIES" , #dropped
    Category == "72" ~ "BOND ISSUE PROCEEDS",  #dropped
    Category == "75" ~  "INTER-AGENCY RECEIPTS ",  #dropped
    Category == "76" ~  "TRANSFER IN FROM OUT FUNDS",  #other
    Category == "78" ~  "ALL OTHER SOURCES" ,
    Category == "79" ~   "COOK COUNTY IGT", #dropped
    Category == "98" ~  "PRIOR YEAR REFUNDS", #dropped
                 T ~ "Check Me!"

  ) )%>% 
  mutate(Category_name = str_to_title(Category_name))


exp_long <- pivot_longer(ff_exp, exp_402:exp_970 , names_to = c("type", "Category"), values_to = "Dollars", names_sep = "_") %>% 
  rename(Year = fy ) %>% 
  mutate(Category_name = 
           case_when(
             Category == "402" ~ "AGING" ,
             Category == "406" ~ "AGRICULTURE", 
             Category == "416" ~ "CENTRAL MANAGEMENT",
             Category == "418" ~ "CHILDREN AND FAMILY SERVICES", 
             Category == "420" ~ "COMMERCE AND ECONOMIC OPPORTUNITY",
             Category == "422" ~ "NATURAL RESOURCES" ,
             Category == "426" ~ "CORRECTIONS",
             Category == "427" ~ "EMPLOYMENT SECURITY" ,
             Category == "444" ~ "HUMAN SERVICES" ,
             Category == "448" ~ "Innovation and Technology", # AWM added fy2022
             Category == "478" ~ "FAMILY SERVICES net Medicaid", 
             Category == "482" ~ "PUBLIC HEALTH", 
             Category == "492" ~ "REVENUE", 
             Category == "494" ~ "TRANSPORTATION" ,
             Category == "532" ~ "ENVIRONMENTAL PROTECT AGENCY" ,
             Category == "557" ~ "IL STATE TOLL HIGHWAY AUTH" ,
             Category == "684" ~ "IL COMMUNITY COLLEGE BOARD", 
             Category == "691" ~ "IL STUDENT ASSISTANCE COMM" ,
             Category == "900" ~ "NOT IN FRAME",
             Category == "901" ~ "STATE PENSION CONTRIBUTION",
             Category == "903" ~ "DEBT SERVICE",
             Category == "904" ~ "State Employee Healthcare",
             Category == "910" ~ "LEGISLATIVE"  ,
             Category == "920" ~ "JUDICIAL" ,
             Category == "930" ~ "ELECTED OFFICERS" , 
             Category == "940" ~ "OTHER HEALTH-RELATED", 
             Category == "941" ~ "PUBLIC SAFETY" ,
             Category == "942" ~ "ECON DEVT & INFRASTRUCTURE" ,
             Category == "943" ~ "CENTRAL SERVICES",
             Category == "944" ~ "BUS & PROFESSION REGULATION" ,
             Category == "945" ~ "MEDICAID" ,
             Category == "946" ~ "CAPITAL IMPROVEMENT" , 
             Category == "948" ~ "OTHER DEPARTMENTS" ,
             Category == "949" ~ "OTHER BOARDS & COMMISSIONS" ,
             Category == "959" ~ "K-12 EDUCATION" ,
             Category == "960" ~ "UNIVERSITY EDUCATION",
             Category == "970" ~ "Local Govt Transfers",
             T ~ "CHECK ME!")
           ) %>% 
  mutate(Category_name = str_to_title(Category_name))

#write_csv(exp_long, "expenditures_recoded_long_FY23.csv")
#write_csv(rev_long, "revenue_recoded_long_FY23.csv")


# combine revenue and expenditures into one data frame
aggregated_totals_long <- rbind(rev_long, exp_long)

aggregated_totals_long %>% mutate(`Dollars (Millions)` = round(Dollars, digits = 0)) %>% select(-Dollars) %>%
  select(Year, Category_name, `Dollars (Millions)`, type, Category)
Table 2.1: Long Version of Data that has Revenue and Expenditures in One Dataframe
Code
year_totals <- aggregated_totals_long %>% 
  group_by(type, Year) %>% 
  summarize(Dollars = sum(Dollars, na.rm = TRUE)) %>% 
  pivot_wider(names_from = "type", values_from = Dollars) %>% 

  rename(Expenditures = exp,
         Revenue = rev) %>%  
  mutate(`Fiscal Gap` = Revenue - Expenditures)
# %>%  arrange(desc(Year))
# creates variable for the Gap each year

year_totals  %>%  
  mutate_all(., ~round(., digits = 0)) %>%
  kbl(caption = "Fiscal Gap for each Fiscal Year ($ Millions)") %>% 
  kable_styling(bootstrap_options = c("striped"))  %>%
  kable_classic() %>%   
  add_footnote(c("Values include State CURE dollars (SLFRF)"))
Table 2.2: Fiscal Gap for each Fiscal Year ($ Millions)
Year Expenditures Revenue Fiscal Gap
1998 31243 32030 787
1999 33846 33966 119
2000 37343 37051 -292
2001 40360 38286 -2074
2002 42068 37922 -4145
2003 42613 38453 -4160
2004 53026 42612 -10414
2005 45362 44306 -1056
2006 48062 46170 -1892
2007 51131 49494 -1637
2008 54173 51643 -2530
2009 56751 51466 -5285
2010 59270 51197 -8073
2011 60423 56303 -4120
2012 59864 58422 -1442
2013 63287 63102 -185
2014 66966 65269 -1697
2015 69941 66590 -3351
2016 63931 64155 223
2017 71728 63660 -8068
2018 74973 73015 -1958
2019 74406 74638 232
2020 81606 80589 -1017
2021 92888 95206 2318
2022 100069 116061 15992
2023 111974 111774 -200
a Values include State CURE dollars (SLFRF)

Graphs made from aggregated_totals_long dataframe.

Code
annotation <- data.frame(
  x = c(2004, 2017, 2019),
  y = c(60, 50, 5),  
  label = c("Expenditures","Revenue", "Fiscal Gap")
)

## Dashed line versions for expenditures: 
library(cmapplot)
fiscal_gap <-   
  ggplot(data = year_totals, aes(x=Year, y = Revenue/1000)) +
  geom_recessions(text = FALSE)+

  # geom_smooth adds regression line, graphed first so it appears behind line graph
  geom_smooth(aes(x = Year, y = Revenue/1000), color = "gray", alpha = 0.7, method = "lm", se = FALSE) + 
  #  scale_linetype_manual(values="dashed")+
  geom_smooth(aes(x = Year, y = Expenditures/1000), color ="rosybrown2", linetype = "dotted", method = "lm", se = FALSE, alpha = 0.7) +

  # line graph of revenue and expenditures
  geom_line(aes(x = Year, y = Revenue/1000), color = "Black", size=1) +
  geom_line(aes(x = Year, y = Expenditures/1000, linetype = "dashed"), color = "red", lwd=1) +
  geom_line(aes(x = Year, y = (`Fiscal Gap`/1000)), color = "darkgray", lwd = 1) +
    geom_hline(yintercept = 0) +

  geom_text(data = annotation, aes(x=x, y=y, label=label))+
  # labels
    theme_classic() +
    theme(legend.position = "none")+
    scale_linetype_manual(values = c("dashed", "dashed")) +
  scale_x_continuous(expand = c(0,0)) +

#  scale_y_continuous(labels = comma)+
  xlab("Year") + 
  ylab("Billions of Dollars")  +
  ggtitle("Illinois Expenditures and Revenue Totals, 1998-2023")

fiscal_gap

# annotation_billions <- data.frame(
#   x = c(2004, 2017, 2019),
#   y = c(60, 50, 5),  
#   label = c("Expenditures","Revenue", "Fiscal Gap"))


fiscal_gap2 <-ggplot(data = year_totals, aes(x=Year, y = Revenue/1000)) +
  geom_recessions(text = FALSE)+
  geom_line(aes(x = Year, y = Revenue/1000), color = "Black", lwd=1) +
  geom_line(aes(x = Year, y = Expenditures/1000, linetype = "dashed"), color = "red", lwd=1) +
  geom_line(aes(x = Year, y = (`Fiscal Gap`/1000)), color = "darkgray", lwd=1) +
  
  geom_text(data = annotation, aes(x=x, y=y, label=label))+
    theme_classic() +
  theme(legend.position = "none")+
    scale_linetype_manual(values = c("dashed", "dashed")) +
  geom_hline(yintercept = 0) +

  scale_y_continuous(labels = comma)+
  scale_x_continuous(expand = c(0,0)) +

  xlab("Year") + 
  ylab("Billions of Dollars")  +
  ggtitle("Illinois Expenditures and Revenue Totals, 1998-2023")


fiscal_gap2



ggsave(plot = fiscal_gap2, filename= "Figure1-fiscalgap.eps")
Figure 2.1: Fiscal Gap Comparison
(a) Fiscal Gap With Trend Lines
(b) Fiscal Gap Without Trend Lines

Expenditure and revenue amounts in billions of dollars:

Code
exp_long %>%
  filter(Year == 2023) %>%
  #mutate(`Total Expenditures`= sum(Dollars, na.rm = TRUE)) %>%
 # select(-c(Year, `Total Expenditures`)) %>%
  arrange(desc(`Dollars`)) %>%
  ggplot() + 
  geom_col(aes(x = fct_reorder(Category_name, `Dollars`), y = (`Dollars`/1000), fill = "red"))+ 
  coord_flip() +
      theme_classic()+
  theme(legend.position = "none") +
  labs(title = "Expenditures for FY2023") +
    xlab("Expenditure Categories") +
  ylab("Billions of Dollars") 

rev_long %>%
  filter(Year == 2023) %>%
  #mutate(`Total Expenditures`= sum(Dollars, na.rm = TRUE)) %>%
 # select(-c(Year, `Total Expenditures`)) %>%
  arrange(desc(`Dollars`)) %>%
  ggplot() + 
  geom_col(aes(x = fct_reorder(Category_name, `Dollars`), y = (`Dollars`/1000)))+ 
  coord_flip() +
    theme_classic() +
    theme(legend.position = "none") +
      labs(title = "Revenues for FY2023")+
    xlab("Revenue Categories") +
  ylab("Billions of Dollars") 
Figure 2.2: FY23 Totals
(a) FY23 Expenditures
(b) FY23 Revenue Sources

Expenditure and revenues when focusing on largest categories and combining others into “All Other Expenditures(Revenues)”:

Code
exp_long %>%
  filter( Year == 2023) %>%
  mutate(rank = rank(Dollars),
        Category_name = ifelse(rank > 13, Category_name, 'All Other Expenditures')) %>%
 # select(-c(Year, Dollars, rank)) %>%
  arrange(desc(Dollars)) %>%
  ggplot() + 
  geom_col(aes(x = fct_reorder(Category_name, `Dollars`), y = `Dollars`/1000), fill = "rosybrown2")+ 
  coord_flip() +
      theme_classic() +
    labs(title = "Expenditures for FY2023") +
    xlab("") +
  ylab("Billions of Dollars")

rev_long %>%
  filter( Year == 2023) %>%
  mutate(rank = rank(Dollars),
        Category_name = ifelse(rank > 10, Category_name, 'All Other Sources')) %>%
  arrange(desc(Dollars)) %>%
  ggplot() + 
  geom_col(aes(x = fct_reorder(Category_name, `Dollars`/1000), y = `Dollars`/1000), fill = "dark gray")+ 
  coord_flip() +
      theme_classic() +
    labs(title = "Revenues for FY2023") +
    xlab("") +
  ylab("Billions of Dollars")
Figure 2.3: Largest Expenditure and Revenue Categories in Illinois FY 2023
(a) Largest Expenditures for FY2023
(b) Largest Revenue Sources for FY2023

Changes in Categories - 2022 to 2023

Code
rev_dotplot <- rev_long %>%
    filter(Year == "2023" | Year == "2022") %>%
  mutate(Year = as.character(Year)) %>%
  ggplot(aes(x = Dollars/1000, y = reorder(Category, Dollars))) +
  geom_line(aes(group = Category) )+
    geom_text(aes(x = ifelse(Year == "2023", as.numeric(Dollars)/1000, NA),  label = ifelse(Year == "2023", Category_name, "")),  
            hjust = -0.2,
            size = 2.8) +
         geom_point(aes(color = Year), size=2)  +
  labs(title = "2022 to 2023 Change in Revenue", x = "Billions of Dollars" , y = "",  caption = "")  +
   scale_fill_manual(values = c("#d62828", "#003049"), labels = c("FY 2022", "FY 2023"))+
    scale_color_manual(values = c("#d62828", "#003049")) +   
  theme_classic()+ 
    theme(
   legend.position = "bottom" ,
  axis.text.y = element_blank(),
  axis.ticks.y = element_blank(),
  axis.line.y.left  = element_blank())+
  scale_x_continuous(limits = c(0, 35), labels = comma)

rev_dotplot

ggsave("./paper-figures/Figure3-revenue-dotplot.eps")

exp_long %>%
    filter(Year == "2023" | Year == "2022") %>%
  mutate(Year = as.character(Year)) %>%
  ggplot(aes(x = Dollars/1000, y = reorder(Category, Dollars))) +
  geom_line(aes(group = Category) )+
  geom_text(aes(x = ifelse(Year == "2023", (as.numeric(Dollars)/1000)+1, NA),  
                label = ifelse(Year == "2023", Category_name, "")),  
            hjust = 0,
            size = 2.8) +
  geom_point(aes(color = Year), size=2 #, alpha = 0.5
             )  +
  labs(title = "2022 to 2023 Change in Expenditures", x = "Millions of Dollars" , y = "",  caption = "")  +
   scale_fill_manual(values = c("#d62828", "#003049"), labels = c("FY 2022", "FY 2023"))+
    scale_color_manual(values = c("#d62828", "#003049")) +
   theme_classic()+ 
    theme(
    legend.position = "bottom" ,
  axis.text.y = element_blank(),
  axis.ticks.y = element_blank(),
  axis.line.y.left  = element_blank() ) +
  scale_x_continuous(limits = c(0, 35), labels = comma)
Figure 2.4: Change from FY22 to FY23
(a) Change in Revenue Sources, FY22 to FY23
(b) Change in Expenditure Categories, FY22 to FY23

2.0.1 Top 3 Own Source Revenues

Code
annotation <- data.frame(
  x = c(2013, 2018, 2013),
  y = c(16, 10, 5),  
  label = c("Individual Income Tax", "Sales Tax", "Corporate Income Tax")
)

top3 <- rev_long  %>% 
  filter(Category == "02" | Category == "03" | Category == "06")

top3 <- ggplot(data = top3, aes(x=Year, y=Dollars/1000))+
      geom_recessions(text = FALSE)+
  geom_line(aes(x=Year, y=Dollars/1000, color = Category_name)) + 
  geom_text(data = annotation, aes(x=x, y=y, label=label)) +
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +
  scale_linetype_manual(values = c("dotted", "dashed", "solid")) +
  theme(legend.position = "none") +
  labs(title = "Top 3 Own Source Revenues", 
       subtitle = "Individual Income Taxes, Sales Tax, and Corporate income taxes",
       y = "Billions of Nominal Dollars") 
  
top3
Figure 2.5: Top 3 Revenue Sources (Own-Source Revenues only)

2.0.1.1 Income Taxes

[[ TO DO: merge fund names to fund codes for past years. Everything before 2014 is missing a fund name ]]

Income taxes include Individual income taxes and corporate income taxes.

Code
rev_temp  %>% 
  filter(rev_type == "03" | rev_type == "02") %>%
  group_by(fy, source, source_name_AWM) %>% 
  summarize(receipts = sum(receipts)) %>%
  ggplot() + # aes(x=fy, y=receipts/1000, group = source))+
     # geom_recessions(text = FALSE)+
  geom_line(aes(x=fy, y=receipts/1000000000, color = source_name_AWM)) + 
  #geom_text(data = annotation, aes(x=x, y=y, label=label)) +
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +
 # scale_linetype_manual(values = c("dotted", "dashed", "solid")) +
  # theme(legend.position = "none") +
  labs(title = "All Income Tax by Revenue Source", 
       subtitle = "Income Taxes include money transfered straight to local governments and funds saved for tax refunds.",
       y = "Billions of Nominal Dollars") 

rev_temp  %>% 
  filter(rev_type == "03" ## |Category == "03" | Category == "06"
         ) %>%
  group_by(fy, fund_name) %>% 
  summarize(receipts = sum(receipts)) %>%
  ggplot() + # aes(x=fy, y=receipts/1000, group = source))+
     # geom_recessions(text = FALSE)+
  geom_line(aes(x=fy, y=receipts/100000000, color = fund_name)) + 
  #geom_text(data = annotation, aes(x=x, y=y, label=label)) +
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +
 # scale_linetype_manual(values = c("dotted", "dashed", "solid")) +
  # theme(legend.position = "none") +
  labs(title = "All Income Tax Money by Receiving Fund", 
       subtitle = "Income Taxes include money transfered straight to local governments and funds saved for tax refunds.",
       y = "Billions of Nominal Dollars")
Figure 2.6: Break down of ALL Income Tax
Figure 2.7: Break down of ALL Income Tax
Code
rev_temp  %>% 
  filter(rev_type == "02"
         ) %>%
  group_by(fy, source, source_name_AWM) %>% 
  summarize(receipts = sum(receipts)) %>%
  ggplot() + 
  geom_line(aes(x=fy, y=receipts/100000000, color = source_name_AWM)) + 
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +
  labs(title = "Income Tax  Breakdown", 
       subtitle = "Individual Income Taxes include money transfered straight to local governments and funds saved for tax refunds.",
       y = "Billions of Nominal Dollars") 

rev_temp  %>% 
  filter(rev_type == "02" ## |Category == "03" | Category == "06"
         ) %>%
  group_by(fy, fund_name) %>% 
  summarize(receipts = sum(receipts)) %>%
  ggplot() + # aes(x=fy, y=receipts/1000, group = source))+
     # geom_recessions(text = FALSE)+
  geom_line(aes(x=fy, y=receipts/100000000, color = fund_name)) + 
  #geom_text(data = annotation, aes(x=x, y=y, label=label)) +
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +
  labs(title = "Individual Income Tax  Breakdown", 
       subtitle = "Individual Income Taxes include money transfered straight to local governments and funds saved for tax refunds.",
       y = "Billions of Nominal Dollars")
Figure 2.8: Break down of Individual Income Tax
Figure 2.9: Break down of Individual Income Tax
Code
rev_temp  %>% 
  filter(rev_type == "03" ) %>%
  group_by(fy, source, source_name_AWM) %>% 
  summarize(receipts = sum(receipts)) %>%
  ggplot() + # aes(x=fy, y=receipts/1000, group = source))+
     # geom_recessions(text = FALSE)+
  geom_line(aes(x=fy, y=receipts/100000000, color = source_name_AWM)) + 
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +
  labs(title = "Corporate Income Tax  Breakdown", 
       subtitle = "Corporate Income Taxes include money transfered straight to local governments and funds saved for tax refunds.",
       y = "Billions of Nominal Dollars") 

rev_temp  %>% 
  filter(rev_type == "03" ## |Category == "03" | Category == "06"
         ) %>%
  group_by(fy, fund_name) %>% 
  summarize(receipts = sum(receipts)) %>%
  ggplot() + 
  geom_line(aes(x=fy, y=receipts/100000000, color = fund_name)) + 
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +

  labs(title = "Corporate Income Tax Breakdown", 
       subtitle = "Corporate Income Taxes include money transfered straight to local governments and funds saved for tax refunds.",
       y = "Billions of Nominal Dollars")
Figure 2.10: Break down of Corporte Income Tax
Figure 2.11: Break down of Corporte Income Tax

2.0.1.2 Sales Tax

Warning

Online Retailer Warning

Not edited or double checked. Randomly looked into online retailers recently and didn’t finish thoughts on it. Just general notes pulled together while looking into online sales tax.

Law was passed in 2018 that required out of state retailers to pay the 6.25% state sales tax. The Rebuild Illinois law expanded the law to require remote retailers to charge all state and local retailers occupation taxes beginning in July 1, 2020. Before Jan. 1 2021, only state sales taxes were required to be collected (related to South Dakota v Wayfair court decision). Now required to pay state and local tax based on where product is delivered.

“On June 28, 2019, Public Act 101-0031, the”Leveling the Playing Field for Illinois Retail Act,” was signed into Illinois law and on December 13, 2019 an amendment to the Act was signed into law in Public Act 101-0604. In an effort to create more equity between remote sellers and local brick-and-mortar retailers, the new law requires remote sellers without a physical presence in the state and marketplace facilitators (e.g., Amazon and Walmart) to collect both state and local sales taxes effective January 1, 2021.” CivicFed.org

Requires remote sellers and marketplace facilitators to collect and remit the state and locally-imposed Retailers’ Occupation Tax (ROT) for the jurisdictions where the product is delivered (destination sourcing) rather than collecting and remitting solely the state use tax. 

Illinois’ State sales tax rate is 6.25%, of which 5.0% of the sales tax revenue goes to the State, 1.0% goes to all municipalities, including Chicago, and the remaining 0.25% goes to the counties. However, Cook County’s 0.25% share of the State sales tax is distributed to the Regional Transportation Authority.

“The amended”Leveling the Playing Field for Illinois Retail Act” was passed by the General Assembly on November 14, 2019, to require both Remote Retailers and Marketplace Facilitators to collect and remit the state and locally-imposed Retailers’ Occupation Tax (ROT, aka sales tax) for the jurisdictions where the product is delivered (its destination) starting January 1, 2021.”- Illinois Municipal League

  • Marketplace Facilitators, like Amazon, were required to collect Use Tax on sales starting January 1, 2020

  • Other sellers required to collect state and local sales tax on sales on January 2021.

  • There is a state tax rate of 6.25% and Illinois municipalities may impose an additional local sales tax called the Retailer’s Occupation Tax.

    • For remote sellers, the state tax rate is referred to as “use tax” and for intrastate sellers, “ROT” simply means sales tax.  

    • The ROT is measured upon the seller’s gross receipts and the seller is statutorily required to collect the use tax from their customers.

  • source 0482 is State ROT-2.2%

ILGA info - leveling the playing field went into effect on July 1 2020 which is the beginning of FY21

Code
## State Retailers Occupation Tax. 
rev_temp %>% filter(source == "0481") %>%
  group_by(fy, source_name_AWM) %>% summarize(revenue=sum(receipts))
Figure 2.12: Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22)
Code
rev_temp %>% 
  filter(source == "0481") %>%
  group_by(fy, source_name_AWM, fund_name_ab) %>% 
  summarize(revenue=sum(receipts))%>%
  arrange(-fy, -revenue)%>%
  pivot_wider(names_from = "fy", values_from="revenue")
Figure 2.13: Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22)
Code
rev_temp %>% 
  filter(source == "0481") %>%
  #group_by(fy, source_name_AWM, fund_name_ab) %>% 
#  summarize(revenue=sum(receipts))  %>% 
  ggplot(aes(x=fy, y=receipts))+
  geom_recessions()+
  geom_line(aes(color=fund_name_ab))+
  geom_vline(xintercept = 2018)+
  geom_vline(xintercept = 2021)+
  theme_classic()+
  scale_x_continuous(expand = c(0,0))
  labs(title="State Retailers' Occupation Tax, Source 0481")
$title
[1] "State Retailers' Occupation Tax, Source 0481"

attr(,"class")
[1] "labels"
Figure 2.14: Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22)

State tax began being collected for remote retailers based on destination beginning in Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22).

Code
### Remote Occupation Tax
# STATE ROT-2.2%
rev_temp %>% 
  filter(source == "0482") %>%
  group_by(fy, source_name_AWM) %>% 
  summarize(revenue=sum(receipts))
Figure 2.15: Large increases due to Leveling the Playing Field Act & Online shopping during pandemic. Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22)
Code
rev_temp %>% 
  filter(source == "0482") %>%
  group_by(fy, source_name_AWM, fund_name_ab) %>% 
  summarize(revenue=sum(receipts))%>%
  arrange(-fy, -revenue)%>%
  pivot_wider(names_from = "fy", values_from="revenue")
Figure 2.16: Large increases due to Leveling the Playing Field Act & Online shopping during pandemic. Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22)
Code
rev_temp %>% 
  filter(source == "0482") %>%
  #group_by(source_name_AWM) %>% 
  #summarize(revenue=sum(receipts))  %>% 
  ggplot(aes(x=fy, y=receipts))+
  geom_line(aes(color=fund_name_ab))+
  geom_recessions()+
  geom_vline(xintercept = 2018)+
  geom_vline(xintercept = 2020)+
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  labs(title="State Retailers' Occupation Tax")

rev_temp %>% 
  filter(source == "0482") %>%
  group_by(fy, source_name_AWM, fund_name_ab) %>% 
  summarize(revenue=sum(receipts))  %>% 
  ggplot()+
  geom_line(aes(x=fy, y=revenue, color=fund_name_ab))+
  geom_vline(xintercept = 2018) +
  geom_vline(xintercept = 2021) +
  #geom_recessions(aes(x=fy, y=receipts)+
  theme_classic()+
  scale_x_continuous(expand = c(0,0)) +
  labs(title="State ROT - 2.2%")

rev_temp %>% 
  filter(source == "0482" | source == "0481") %>%
  group_by(fy, source_name_AWM) %>% 
  summarize(revenue=sum(receipts))  %>% 
  ggplot()+
  geom_line(aes(x=fy, y=revenue, color=source_name_AWM))+
  geom_vline(xintercept = 2018)+
  geom_vline(xintercept = 2021)+
  #geom_recessions(aes(x=fy, y=receipts)+
  theme_classic()+
  scale_x_continuous(expand = c(0,0)) + 

  labs(title="State ROT - 2.2% & ",
       subtitle = "Large increases due to Leveling the Playing Field Act & Online shopping during pandemic??")
Figure 2.17: Large increases due to Leveling the Playing Field Act & Online shopping during pandemic. Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22)
Figure 2.18: Large increases due to Leveling the Playing Field Act & Online shopping during pandemic. Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22)
Figure 2.19: Large increases due to Leveling the Playing Field Act & Online shopping during pandemic. Leveling the Playing Field went into effect for Amazon on January 1, 2020(mid-FY21) and for other remote retailers starting January 1, 2021 (mid-FY22)

As of Feb. 6 2023, Source 481 Retailers Occupation Tax has collected $9.3 billion already. FY22 had $14.7 million. Around half goes to the General Revenue Fund.

Code
rev_temp  %>% 
  filter(rev_type == "06" ) %>%
  group_by(fy, source, source_name_AWM) %>% 
  summarize(receipts = round(sum(receipts))) %>% 
  pivot_wider(names_from = "fy", values_from = "receipts")
rev_temp  %>% 
  filter(rev_type == "06" ) %>%
  group_by(fy, source, source_name_AWM) %>% 
  summarize(receipts = sum(receipts)) %>%
  ggplot() + 
  geom_line(aes(x=fy, y=receipts/100000000, color = source_name_AWM)) + 
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +
  theme(legend.position = "bottom") +
  labs(title = "Sales Tax Breakdown", 
       y = "Billions of Nominal Dollars") 

rev_temp  %>% 
  filter(rev_type == "06" & fy > 2015) %>%
  group_by(fy, fund_name) %>% 
  summarize(receipts = sum(receipts)) %>%
  ggplot() + # aes(x=fy, y=receipts/1000, group = source))+
     # geom_recessions(text = FALSE)+
  geom_line(aes(x=fy, y=receipts/100000000, color = fund_name)) + 
  #geom_text(data = annotation, aes(x=x, y=y, label=label)) +
  theme_classic() +
  scale_x_continuous(expand = c(0,0)) +
  scale_y_continuous(labels = comma) +
 # scale_linetype_manual(values = c("dotted", "dashed", "solid")) +
  theme(legend.position = "bottom") +
  labs(title = "Sales Tax Breakdown", 
       #subtitle = "Sales Taxes include money transfered straight to local governments and funds saved for tax refunds.",
       y = "Billions of Nominal Dollars")
Figure 2.20: Break down of Sales Tax. Sales Taxes include money transfered straight to local governments and funds saved for tax refunds.
(a)
(b)
(c)

2.0.2 Own Source and Fed Transfers

Code
ownsource_rev <- rev_long %>%
  filter(!Category %in% c("57", "58", "59")) %>%
  group_by(Year) %>% 
  summarize(Dollars = sum(Dollars))

# ownsource_rev %>% 
#   ggplot()+geom_line(aes(x=Year, y=Dollars)) + 
#   labs(title = "Own Source Revenues", subtitle = "Total own source revenue", y = "Millions of Dollars")

fed_rev <- ff_rev %>% select(fy, rev_57, rev_58, rev_59) %>%
  mutate(fed_total = rev_57+rev_58+rev_59)


annotation <- data.frame(
  x = c(2014, 2015),
  y = c(50, 25),  
  label = c("Own Source Revenue", "Federal Revenue")
)


ownsource_graph <- ggplot(ownsource_rev, aes(x=Year, y=Dollars/1000)) + 
  geom_recessions( text = FALSE)+
  geom_line(data = ownsource_rev, aes(x=Year, y=Dollars/1000), color = "Red") + 
  geom_line(data = fed_rev, aes(x=fy, y=fed_total/1000), color = "Black") + 
    geom_text(data = annotation, aes(x=x, y=y, label=label))+
    scale_y_continuous(labels = comma)+
  scale_x_continuous(expand = c(0,0)) + 
  theme(legend.position = "none")+

  theme_classic()+
  labs(title = "Own Source Revenue and Federal Revenue", 
  y = "Billions of Nominal Dollars")
ownsource_graph

# ggsave(plot = ownsource_graph, file = "Figure4.eps")
Figure 2.21: Comparison of Own Source and Federal Revenue. Historicaly, federal revenue tends to increase when state revenue decreases from some sort of economic shock (e.g. Housing Bubble in 2008).

3 Change from Previous Year

Each year, you will need to update the CAGR formulas! Change the filter() year.

calc_cagr is a function created for calculating the CAGRs for different spans of time.

Code
exp_totals <- ff_exp %>% 
  rowwise() %>% 
  mutate(exp_TOTALS = sum(across(exp_402:exp_970)))

rev_totals <- ff_rev %>%    
  rowwise() %>% 
  mutate(rev_TOTALS = sum(across(rev_02:rev_78)))



rev_long <- pivot_longer(rev_totals, rev_02:rev_TOTALS, names_to = c("type","Category"), values_to = "Dollars", names_sep = "_") %>% 
  rename(Year = fy) %>%
  mutate(Category_name = case_when(
    Category == "02" ~ "INDIVIDUAL INCOME TAXES" ,
    Category == "03" ~ "CORPORATE INCOME TAXES" ,
    Category == "06" ~ "SALES TAXES" ,
    Category == "09" ~ "MOTOR FUEL TAX" ,
    Category == "12" ~ "PUBLIC UTILITY TAXES" ,
    Category == "15" ~ "CIGARETTE TAXES" ,
    Category == "18" ~ "LIQUOR GALLONAGE TAXES" ,
    Category == "21" ~ "INHERITANCE TAX" ,
    Category == "24" ~ "INSURANCE TAXES&FEES&LICENSES" ,
    Category == "27" ~ "CORP FRANCHISE TAXES & FEES" ,
    Category == "30" ~ "HORSE RACING TAXES & FEES",  # in Other
    Category == "31" ~ "MEDICAL PROVIDER ASSESSMENTS" ,
    Category == "32" ~ "GARNISHMENT-LEVIES" , # dropped
    Category == "33" ~  "LOTTERY RECEIPTS" ,
    Category == "35" ~  "OTHER TAXES" ,
    Category == "36" ~  "RECEIPTS FROM REVENUE PRODUCING", 
    Category == "39" ~  "LICENSES, FEES & REGISTRATIONS" ,
    Category == "42" ~  "MOTOR VEHICLE AND OPERATORS" ,
    Category == "45" ~  "STUDENT FEES-UNIVERSITIES",   # dropped
    Category == "48" ~  "RIVERBOAT WAGERING TAXES" ,
    Category == "51" ~  "RETIREMENT CONTRIBUTIONS" , # dropped
    Category == "54" ~ "GIFTS AND BEQUESTS", 
    Category == "57" ~  "FEDERAL OTHER" ,
    Category == "58" ~  "FEDERAL MEDICAID", 
    Category == "59" ~  "FEDERAL TRANSPORTATION" ,
    Category == "60" ~  "OTHER GRANTS AND CONTRACTS", #other
    Category == "63" ~  "INVESTMENT INCOME", # other
    Category == "66" ~ "PROCEEDS,INVESTMENT MATURITIES" , #dropped
    Category == "72" ~ "BOND ISSUE PROCEEDS",  #dropped
    Category == "75" ~  "INTER-AGENCY RECEIPTS ",  #dropped
    Category == "76" ~  "TRANSFER IN FROM OUT FUNDS",  #other
    Category == "78" ~  "ALL OTHER SOURCES" ,
    Category == "79" ~   "COOK COUNTY IGT", #dropped
    Category == "98" ~  "PRIOR YEAR REFUNDS", #dropped
    Category == "TOTALS" ~ "Total",
    T ~ "CHECK ME"
    
  ) ) %>% 
  select(-type, -Category) %>%  # drop extra columns type and Category number
  group_by(Year, Category_name) %>%
  summarise(Dollars= sum(Dollars)) %>% 
  mutate(Category_name = str_to_title(Category_name))

# creates wide version of table where each revenue source is a column
revenue_wide2 <- rev_long %>% pivot_wider(names_from = Category_name, 
                                          values_from = Dollars) %>%
  #  relocate("Other Revenue Sources **", .after = last_col()) %>%
  relocate("Total", .after =  last_col())

exp_long <- pivot_longer(exp_totals, exp_402:exp_TOTALS , names_to = c("type", "Category"), values_to = "Dollars", names_sep = "_") %>%
  rename(Year = fy ) %>% 
  mutate(Category_name = 
           case_when(
             Category == "402" ~ "AGING" ,
             Category == "406" ~ "AGRICULTURE", 
             Category == "416" ~ "Central Management",
             Category == "418" ~ "CHILDREN AND FAMILY SERVICES", 
             Category == "420" ~ "Community Development",
             Category == "422" ~ "NATURAL RESOURCES" ,
             Category == "426" ~ "CORRECTIONS",
             Category == "427" ~ "EMPLOYMENT SECURITY" ,
             Category == "444" ~ "Human Services" ,
             Category == "478" ~ "HEALTHCARE & FAM SER NET OF MEDICAID", 
             Category == "482" ~ "PUBLIC HEALTH", 
             Category == "492" ~ "REVENUE", 
             Category == "494" ~ "Transportation" ,
             Category == "532" ~ "ENVIRONMENTAL PROTECT AGENCY" ,
             Category == "557" ~ "Tollway" ,
             Category == "684" ~ "IL COMMUNITY COLLEGE BOARD", 
             Category == "691" ~ "IL STUDENT ASSISTANCE COMM" ,
             Category == "900" ~ "NOT IN FRAME",
             Category == "901" ~ "State Pension Contribution",
             Category == "903" ~ "Debt Service",
             Category == "904" ~ "State Employee Healthcare",
             Category == "910" ~ "LEGISLATIVE"  ,
             Category == "920" ~ "JUDICIAL" ,
             Category == "930" ~ "ELECTED OFFICERS" , 
             Category == "940" ~ "OTHER HEALTH-RELATED", 
             Category == "941" ~ "Public Safety" ,
             Category == "942" ~ "ECON DEVT & INFRASTRUCTURE" ,
             Category == "943" ~ "CENTRAL SERVICES",
             Category == "944" ~ "BUS & PROFESSION REGULATION" ,
             Category == "945" ~ "Medicaid" ,
             Category == "946" ~ "Capital Improvement" , 
             Category == "948" ~ "OTHER DEPARTMENTS" ,
             Category == "949" ~ "OTHER BOARDS & COMMISSIONS" ,
             Category == "959" ~ "K-12 Education" ,
             Category == "960" ~ "UNIVERSITY EDUCATION",
             Category == "970" ~ "Local Govt Revenue Sharing",
             Category == "TOTALS" ~ "Total",
             T ~ "CHECK ME" 
             # T ~ "All Other Expenditures **")
           )) %>% 
  select(-type, -Category) %>% 
  group_by(Year, Category_name) %>% 
  summarise(Dollars= sum(Dollars)) %>% 
  mutate(Category_name = str_to_title(Category_name))

expenditure_wide2 <- exp_long%>% 
  pivot_wider(names_from = Category_name, 
              values_from = Dollars) %>%
  relocate("Total", .after =  last_col())


# function for calculating the CAGR
calc_cagr <- function(df, n) {
  df <- exp_long %>%
    #select(-type) %>%
    arrange(Category_name, Year) %>%
    group_by(Category_name) %>%
    mutate(cagr = ((`Dollars` / lag(`Dollars`, n)) ^ (1 / n)) - 1)
  
  return(df)
}

Things to do when updating the code:

  • Each year, you need to increase the cagr value by 1. The value should be the (current year - 1998). For FY23, this is 2023-1998 = 25. So all cagr values that were 24 will be changed to 25.
Code
# This works for one variable at a time
cagr_25 <- calc_cagr(exp_long, 25) %>% 
  # group_by(Category) %>%
  summarize(cagr_25 = sum(cagr*100, na.rm = TRUE))

# doesn't need to be changed since it is just pre-covid 
cagr_precovid <- exp_long %>%
  filter(Year <= 2019) %>%
  calc_cagr(21) %>% 
  summarize(cagr_21 = sum(cagr*100, na.rm = TRUE))


# Update year in the filter() and summarize() commands to current year.
cagr_10 <- calc_cagr(exp_long, 10) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_10 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_5 <- calc_cagr(exp_long, 5) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_5 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_3 <- calc_cagr(exp_long, 3) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_3 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_2 <- calc_cagr(exp_long, 2) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_2 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_1 <- calc_cagr(exp_long, 1) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_1 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

# update variables so cagr_24 becomes cagr_25
CAGR_expenditures_summary_tot <- data.frame(cagr_1, cagr_2, cagr_3, cagr_5, cagr_10, cagr_25 ) %>% 
  select(-c(Category_name.1, Category_name.2, Category_name.3, Category_name.4, Category_name.5 )) %>% 
  rename("Expenditure Category" = Category_name, "1 Year CAGR" = cagr_1, "2 Year CAGR" = cagr_2, "3 Year CAGR" = cagr_3, "5 Year CAGR" = cagr_5, "10 Year CAGR" = cagr_10,"25 Year CAGR" = cagr_25 )

move_to_last <- function(df, n) df[c(setdiff(seq_len(nrow(df)), n), n), ]

CAGR_expenditures_summary_tot <- move_to_last(CAGR_expenditures_summary_tot, 29
                                          ) 

#CAGR_expenditures_summary_tot <-   select(CAGR_expenditures_summary_tot, -1) 

CAGR_expenditures_summary_tot %>%   
  kbl(caption = "CAGR Calculations for All Expenditure Categories" , row.names=FALSE) %>% 
  kable_classic() %>%
  row_spec(31, bold = T, color = "black", background = "gray")
Table 3.1: Expenditure Category CAGRs with Total CAGR (Ordered Alphabetically)
CAGR Calculations for All Expenditure Categories
Expenditure Category 1 Year CAGR 2 Year CAGR 3 Year CAGR 5 Year CAGR 10 Year CAGR 25 Year CAGR
Aging 17.5035411 9.8457758 9.0190563 7.6611048 2.0971911 7.7199948
Agriculture -0.6141173 14.8095718 7.1764666 0.9756433 2.4330458 0.8148231
Bus & Profession Regulation 5.8737039 6.3902916 5.7462402 3.6453152 -1.7233466 1.6437697
Capital Improvement 52.5716256 19.3875834 27.9985984 23.5713506 2.0532209 3.8042304
Central Management 17.7481439 7.0076778 4.6446092 6.1625346 4.0153221 4.7587642
Children And Family Services 25.9859009 10.0344085 8.4022688 7.7795747 3.3335986 0.7766504
Community Development 7.7125186 -5.4550293 34.1826333 24.6274817 4.3415932 4.7917662
Corrections 13.4597177 2.7976240 3.4710988 -2.6497864 3.1274136 2.2089231
Debt Service -2.8871960 -1.8637196 0.0719999 -0.1962110 0.7950780 5.7307260
Elected Officers 7.9687686 5.7079431 6.1635365 4.8097186 4.5148083 3.8338636
Employment Security -1.3937521 -9.0387366 4.6257926 2.7518913 0.1727037 1.6197832
Environmental Protect Agency -5.6787426 -4.4268174 -5.0072060 -7.4224395 1.3759219 2.7843331
Healthcare & Fam Ser Net Of Medicaid 13.3679099 4.2886027 6.7929833 0.7660104 0.9310957 5.4599716
Human Services 21.3386342 16.1025281 13.7662650 10.1865061 4.6360395 3.2865014
Judicial 20.7014347 8.5563434 8.5910012 7.2909927 5.6499575 3.3741322
K-12 Education 9.8602395 9.8635931 9.1469312 7.0394792 5.3528615 4.3478887
Legislative 66.4475105 41.1365405 27.7382799 18.7221822 5.7610202 5.1868747
Local Govt Revenue Sharing 5.3563005 23.2327665 19.0836206 12.0172945 6.4612497 4.6775563
Medicaid 13.1042564 11.0519496 13.2863258 11.7590340 8.1698752 7.4322089
Natural Resources 10.4175325 5.2166503 4.9930307 4.8507057 2.8054925 1.9459070
Other Boards & Commissions 33.5371595 14.9663325 15.8496882 8.4549000 1.5296670 5.1067532
Other Departments 16.2181031 8.6532480 8.3776943 8.0594740 6.0284260 9.3574390
Public Health -8.3751873 -9.7855673 11.0700339 15.1321943 7.7647282 6.4426660
Public Safety 1.1798222 -6.8720189 5.3743040 14.1244124 7.5811865 5.6904569
Revenue 66.6779115 34.8510374 39.1559319 41.4186363 22.0196955 8.1401232
State Employee Healthcare -0.0947205 2.0774249 0.2291473 -12.4944084 3.2765846 5.8208663
State Pension Contribution 5.0581029 10.1158161 8.8529994 9.5447336 9.0593992 10.5213967
Tollway -9.8915774 -1.7810457 -0.4137358 5.2341512 7.1403223 6.7795355
Transportation 15.8132934 -0.8141654 9.0562698 8.0231466 2.1131622 4.0178977
University Education 12.5185792 7.4953331 5.0065012 4.6029539 0.9849009 0.8145729
Total 11.8967130 9.7938493 11.1216626 8.3534453 5.8718080 5.2384823
Code
# revenue version function:
calc_cagr <- function(df, n) {
  df <- rev_long %>%
    arrange(Category_name, Year) %>%
    group_by(Category_name) %>%
    mutate(cagr = ((Dollars / lag(Dollars, n)) ^ (1 / n)) - 1)

  return(df)
}

# This works for one variable at a time
cagr_25 <- calc_cagr(rev_long, 25) %>% 
     # group_by(Category) %>%
  summarize(cagr_25 = sum(cagr*100, na.rm = TRUE))

cagr_10 <- calc_cagr(rev_long, 10) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_10 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_5 <- calc_cagr(rev_long, 5) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_5 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_3 <- calc_cagr(rev_long, 3) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_3 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_2 <- calc_cagr(rev_long, 2) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_2 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

 cagr_1 <- calc_cagr(rev_long, 1) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_1 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

CAGR_revenue_summary_tot <- data.frame(cagr_1, cagr_2, cagr_3, cagr_5, cagr_10, cagr_25) %>%   
  select(-c(Category_name.1, Category_name.2, Category_name.3, Category_name.4, Category_name.5 )) %>% 
  rename("Revenue Category" = Category_name, "1 Year CAGR" = cagr_1, "2 Year CAGR" = cagr_2, "3 Year CAGR" = cagr_3, "5 Year CAGR" = cagr_5, "10 Year CAGR" = cagr_10,"25 Year CAGR" = cagr_25 )

CAGR_revenue_summary_tot <- move_to_last(CAGR_revenue_summary_tot,1)
CAGR_revenue_summary_tot <- move_to_last(CAGR_revenue_summary_tot,22)

CAGR_revenue_summary_tot %>% 
  kbl(caption = "CAGR Calculations for All Revenue Sources (Ordered Alphabetical)", row.names = FALSE) %>% 
   kable_classic() %>%
    row_spec(23, bold = T, color = "black", background = "gray")
Table 3.2: Revenue Category CAGRs with Total CAGR (Ordered Alphabetically)
CAGR Calculations for All Revenue Sources (Ordered Alphabetical)
Revenue Category 1 Year CAGR 2 Year CAGR 3 Year CAGR 5 Year CAGR 10 Year CAGR 25 Year CAGR
Cigarette Taxes -6.7158764 -7.487236 -2.6441430 0.5324819 -0.8677937 2.1226172
Corp Franchise Taxes & Fees 4.3978089 -15.992652 2.2646371 1.6579192 0.9653035 2.6195158
Corporate Income Taxes 4.5563120 32.278053 42.3235783 24.8790961 8.9517969 7.1613297
Federal Medicaid 6.0914463 7.281008 13.4373126 8.4399008 9.3236555 7.4666007
Federal Other -43.8692289 8.684250 3.8784811 13.3947814 5.6667300 4.3547331
Federal Transportation 15.3002127 -5.742851 5.8317385 5.8205519 2.1739930 3.7849258
Gifts And Bequests 13.1694280 18.343922 31.7253276 11.7043855 10.4013486 11.4979042
Individual Income Taxes -4.2803888 2.735038 8.8940553 6.1856097 4.3466223 5.3687427
Inheritance Tax -16.6815296 5.713271 21.0703777 7.0170219 5.5342867 2.8261114
Insurance Taxes&Fees&Licenses 8.5621827 2.252488 11.3377657 3.2851985 3.8165814 6.6535717
Licenses, Fees & Registrations 9.2467223 2.238833 13.2342540 11.0722031 6.1803447 7.9441390
Liquor Gallonage Taxes -1.1575970 0.669798 1.4670155 1.3241025 1.2288846 7.0923432
Lottery Receipts 11.8849304 2.459898 10.3683820 4.3219483 1.2250175 2.5270960
Medical Provider Assessments 9.4977740 3.597628 5.5797278 13.1480627 9.4564148 8.4089494
Motor Fuel Tax 1.6564234 3.819450 3.4752678 13.4350273 7.3863307 2.7603528
Motor Vehicle And Operators -0.0101684 -2.839581 3.0824869 1.4935575 0.8867305 3.0838397
Other Taxes 12.9887979 36.080348 25.7960948 14.7204009 16.5078246 7.9025828
Public Utility Taxes 2.2257176 2.658787 0.4449874 0.1643403 -0.4311575 0.7634011
Receipts From Revenue Producing 8.4703645 5.703997 5.9945090 0.8918354 2.6875353 5.2076338
Riverboat Wagering Taxes 9.3638529 40.605002 2.3213535 -4.2202273 -3.8160213 2.0487047
Sales Taxes 4.8358106 8.040426 9.7452003 6.6729036 4.7129001 3.3035471
All Other Sources 50.6516892 44.031253 24.3126796 14.2083484 10.5736818 6.0759994
Total -3.6937612 8.352330 11.5208020 8.8896256 5.8839314 5.1262472

Update all years in mutate() commands so that they all go up by 1:

Code
revenue_change2 <- rev_long %>%
  #select(-c(Category)) %>%
  filter(Year > 2021) %>%
  pivot_wider(names_from = Year , values_from = Dollars,   names_prefix = "Dollars_") %>%
  mutate(
    "FY 2023 ($ billions)" = Dollars_2023/1000,
    "FY 2022 ($ billions)" = Dollars_2022/1000,

#    "Change from 2022 to 2023" = round(Dollars_2022 - Dollars_2021, digits = 2),
         "1-Year Change" = ((Dollars_2023 -Dollars_2022)/Dollars_2022*100)) %>%
  left_join(CAGR_revenue_summary_tot, by = c("Category_name" = "Revenue Category")) %>% 
    arrange(-`FY 2023 ($ billions)`)%>%
#  filter(Category_ame != "NA") %>%
  #select(-c(Dollars_2021, Dollars_2021, `1 Year CAGR`:`10 Year CAGR`)) %>%
  rename( "25-Year CAGR" = `25 Year CAGR`, 
          "Revenue Category" = Category_name ) %>%
  select(-c(Dollars_2022, Dollars_2023, `1 Year CAGR`:`10 Year CAGR`)) 


revenue_change2 <- move_to_last(revenue_change2,8)
revenue_change2 <- move_to_last(revenue_change2,1)

revenue_change2 %>%   
  filter(!is.na(`Revenue Category`)) %>%

  kbl(caption = "Table 1. Yearly Change in Revenue", row.names = FALSE) %>% 
   kable_classic() %>%
    row_spec(23, bold = T, color = "black", background = "gray")
Table 1. Yearly Change in Revenue
Revenue Category FY 2023 ($ billions) FY 2022 ($ billions) 1-Year Change 25-Year CAGR
Individual Income Taxes 25.3095421 26.4413340 -4.2803888 5.3687427
Federal Medicaid 20.2051384 19.0450212 6.0914463 7.4666007
Sales Taxes 16.2224660 15.4741647 4.8358106 3.3035471
Federal Other 10.8787198 19.3810268 -43.8692289 4.3547331
Corporate Income Taxes 10.4843267 10.0274450 4.5563120 7.1613297
Medical Provider Assessments 4.0883799 3.7337562 9.4977740 8.4089494
Receipts From Revenue Producing 2.5890994 2.3869187 8.4703645 5.2076338
Motor Fuel Tax 2.5692288 2.5273649 1.6564234 2.7603528
Federal Transportation 2.1141018 1.8335628 15.3002127 3.7849258
Gifts And Bequests 2.0983858 1.8541984 13.1694280 11.4979042
Licenses, Fees & Registrations 2.0605991 1.8861885 9.2467223 7.9441390
Other Taxes 1.6383366 1.4499991 12.9887979 7.9025828
Motor Vehicle And Operators 1.5971268 1.5972892 -0.0101684 3.0838397
Lottery Receipts 1.5577372 1.3922672 11.8849304 2.5270960
Public Utility Taxes 1.4432122 1.4117898 2.2257176 0.7634011
Cigarette Taxes 0.7848613 0.8413664 -6.7158764 2.1226172
Insurance Taxes&Fees&Licenses 0.6623194 0.6100830 8.5621827 6.6535717
Inheritance Tax 0.5026653 0.6033060 -16.6815296 2.8261114
Riverboat Wagering Taxes 0.3494880 0.3195645 9.3638529 2.0487047
Liquor Gallonage Taxes 0.3162962 0.3200005 -1.1575970 7.0923432
Corp Franchise Taxes & Fees 0.2343405 0.2244688 4.3978089 2.6195158
All Other Sources 4.0679515 2.7002362 50.6516892 6.0759994
Total 111.7743227 116.0613519 -3.6937612 5.1262472
Code
expenditure_change2 <- exp_long %>%
  #select(-c(type,Category)) %>%
  filter(Year > 2021) %>%
  pivot_wider(names_from = Year , values_from = Dollars,   names_prefix = "Dollars_") %>%
  mutate(
    "FY 2023 ($ billions)" = Dollars_2023/1000,
    "FY 2022 ($ billions)" = Dollars_2022/1000,

  #  "Change from 2021 to 2022" = Dollars_2022 - Dollars_2021,
         "1-Year Change" = (Dollars_2023 -Dollars_2022)/Dollars_2022*100 )%>%
  left_join(CAGR_expenditures_summary_tot, by = c("Category_name" = "Expenditure Category")) %>% 
  arrange(-`FY 2023 ($ billions)`)%>%
  select(-c(Dollars_2023, Dollars_2022, `1 Year CAGR`:`10 Year CAGR`)) %>%

  rename( "25-Year CAGR" = `25 Year CAGR`, 
          "Expenditure Category" = Category_name )

expenditure_change2 <- move_to_last(expenditure_change2, 1)

expenditure_change2 %>% 

 filter(!is.na(`Expenditure Category`)) %>%

  kbl(caption = "Table 2. Yearly Change in Expenditures - All FF Categories, Ordered from Largest to Smallest Expenditure Amount", row.names = FALSE) %>% 
  kable_classic() %>%
    row_spec(31, bold = T, color = "black", background = "gray")
Table 2. Yearly Change in Expenditures - All FF Categories, Ordered from Largest to Smallest Expenditure Amount
Expenditure Category FY 2023 ($ billions) FY 2022 ($ billions) 1-Year Change 25-Year CAGR
Medicaid 32.4064127 28.6518065 13.1042564 7.4322089
K-12 Education 14.6979121 13.3787366 9.8602395 4.3478887
Local Govt Revenue Sharing 10.9013002 10.3470796 5.3563005 4.6775563
Human Services 8.8295506 7.2767842 21.3386342 3.2865014
State Pension Contribution 6.8180330 6.4897735 5.0581029 10.5213967
Other Departments 5.6518296 4.8631232 16.2181031 9.3574390
Transportation 5.2949939 4.5720088 15.8132934 4.0178977
Revenue 3.1130811 1.8677227 66.6779115 8.1401232
State Employee Healthcare 3.0023629 3.0052095 -0.0947205 5.8208663
University Education 2.5498722 2.2661788 12.5185792 0.8145729
Debt Service 1.9514367 2.0094535 -2.8871960 5.7307260
Tollway 1.8939298 2.1018343 -9.8915774 6.7795355
Public Safety 1.7416551 1.7213463 1.1798222 5.6904569
Corrections 1.7042905 1.5021107 13.4597177 2.2089231
Children And Family Services 1.5777599 1.2523306 25.9859009 0.7766504
Community Development 1.4984197 1.3911286 7.7125186 4.7917662
Aging 1.3800784 1.1744994 17.5035411 7.7199948
Central Management 1.3641677 1.1585471 17.7481439 4.7587642
Elected Officers 1.0652947 0.9866693 7.9687686 3.8338636
Public Health 0.7673299 0.8374696 -8.3751873 6.4426660
Capital Improvement 0.6526195 0.4277463 52.5716256 3.8042304
Environmental Protect Agency 0.6163407 0.6534484 -5.6787426 2.7843331
Judicial 0.6113109 0.5064653 20.7014347 3.3741322
Healthcare & Fam Ser Net Of Medicaid 0.4273511 0.3769595 13.3679099 5.4599716
Other Boards & Commissions 0.3276571 0.2453677 33.5371595 5.1067532
Natural Resources 0.3220311 0.2916485 10.4175325 1.9459070
Employment Security 0.2708952 0.2747241 -1.3937521 1.6197832
Bus & Profession Regulation 0.2318346 0.2189728 5.8737039 1.6437697
Legislative 0.2126408 0.1277525 66.4475105 5.1868747
Agriculture 0.0915881 0.0921540 -0.6141173 0.8148231
Total 111.9739800 100.0690521 11.8967130 5.2384823

3.1 Summary Tables - Largest Categories

The 10 largest revenue sources and 15 largest expenditure sources remain separate categories and all other smaller sources/expenditures are combined into “All Other Revenues (Expenditures)”. These condensed tables are typically used in the Fiscal Futures articles. They were manually created in past years but this hopefully automates the process a bit until final formatting stages.

  • take ff_rev and ff_exp data frames, which were in wide format, pivot them longer and mutate the Category_name variable to nicer labels. Keep largest categories separate and aggregate the rest.
  • You need to manually comment out the categories that are not the largest each year. Check and compare to the previous years largest categories!
Code
exp_totals <- ff_exp %>% rowwise() %>% mutate(exp_TOTALS = sum(across(exp_402:exp_970))) # creates total column too

rev_totals <- ff_rev %>% rowwise() %>%  mutate(rev_TOTALS = sum(across(rev_02:rev_78)))

rev_long_majorcats <- pivot_longer(rev_totals, rev_02:rev_TOTALS, names_to = c("type","Category"), values_to = "Dollars", names_sep = "_") %>% 
  rename(Year = fy) %>%
  mutate(Category_name = case_when(
    Category == "02" ~ "Income Tax" ,
    Category == "03" ~ "Corporate Income Tax" ,
    Category == "06" ~ "Sales Tax" ,
    Category == "09" ~ "Motor Fuel Taxes" ,
    #   Category == "12" ~ "PUBLIC UTILITY TAXES, gross of PPRT" ,
    #  Category == "15" ~ "CIGARETTE TAXES" ,
    #   Category == "18" ~ "LIQUOR GALLONAGE TAXES" ,
    #  Category == "21" ~ "INHERITANCE TAX" ,
    #  Category == "24" ~ "INSURANCE TAXES&FEES&LICENSES, net of refunds " ,
    # Category == "27" ~ "CORP FRANCHISE TAXES & FEES" ,
    #   Category == "30" ~ "HORSE RACING TAXES & FEES",  # in Other
    Category == "31" ~ "Medical Provider Assessments" ,
    #  Category == "32" ~ "GARNISHMENT-LEVIES" , # dropped
    #  Category == "33" ~  "LOTTERY RECEIPTS" ,
    # Category == "35" ~  "OTHER TAXES" ,
    Category == "36" ~  "Receipts from Revenue Producing", 
    Category == "39" ~  "Licenses, Fees, Registration" ,
    # Category == "42" ~  "MOTOR VEHICLE AND OPERATORS" ,
    #    Category == "45" ~  "STUDENT FEES-UNIVERSITIES",   # dropped
    #    Category == "48" ~  "RIVERBOAT WAGERING TAXES" ,
    #  Category == "51" ~  "RETIREMENT CONTRIBUTIONS" , # dropped
    # Category == "54" ~ "GIFTS AND BEQUESTS", 
    Category == "57" ~  "Federal Other" ,
    Category == "58" ~  "Federal Medicaid Reimbursements", 
    Category == "59" ~  "Federal Transportation" ,
    #   Category == "60" ~  "OTHER GRANTS AND CONTRACTS", #other
    #    Category == "63" ~  "INVESTMENT INCOME", # other
    #   Category == "66" ~ "PROCEEDS,INVESTMENT MATURITIES" , #dropped
    #   Category == "72" ~ "BOND ISSUE PROCEEDS",  #dropped
    #   Category == "75" ~  "INTER-AGENCY RECEIPTS ",  #dropped
    #   Category == "76" ~  "TRANSFER IN FROM OUT FUNDS",  #other
    # Category == "78new" ~  "ALL OTHER SOURCES" ,
    # Category == "79" ~   "COOK COUNTY IGT", #dropped
    #   Category == "98" ~  "PRIOR YEAR REFUNDS", #dropped
    
    Category == "TOTALS" ~ "Total Revenue",
    T ~ "All Other Sources **" # any other Category number that was not specifically referenced is combined into Other Revenue Sources
    
  ) ) %>% 
  select(-type, -Category) %>%  # drop extra columns type and Category number
  group_by(Year, Category_name) %>%
  summarise(Dollars= sum(Dollars)) 

# revenue_wide # not actually in wide format yet. 
# has 10 largest rev sources separate and combined all others to Other in long data format. 


# creates wide version of table where each revenue source is a column
revenue_wide_majorcats <- rev_long_majorcats %>% 
  pivot_wider(names_from = Category_name, 
              values_from = Dollars) %>%
  relocate("All Other Sources **", .after = last_col()) %>%
  relocate("Total Revenue", .after =  last_col()) 


exp_long_majorcats <- pivot_longer(exp_totals, exp_402:exp_TOTALS , names_to = c("type", "Category"), values_to = "Dollars", names_sep = "_") %>% 
  rename(Year = fy ) %>% 
  mutate(Category_name = 
           case_when(
          #   Category == "402" ~ "Aging" ,
          #  Category == "406" ~ "AGRICULTURE", 
         #    Category == "416" ~ "Central Management",
             Category == "418" ~ "Children & Family Services", 
             Category == "420" ~ "Community Development",
           #  Category == "422" ~ "NATURAL RESOURCES" ,
             Category == "426" ~ "Corrections",
           #  Category == "427" ~ "EMPLOYMENT SECURITY" ,
             Category == "444" ~ "Human Services" ,
           #  Category == "478" ~ "HEALTHCARE & FAM SER NET OF MEDICAID", 
           #  Category == "482" ~ "PUBLIC HEALTH", 
             Category == "492" ~ "Revenue", 
             Category == "494" ~ "Transportation" ,
           #  Category == "532" ~ "ENVIRONMENTAL PROTECT AGENCY" ,
             Category == "557" ~ "Tollway" ,
           #  Category == "684" ~ "IL COMMUNITY COLLEGE BOARD", 
            # Category == "691" ~ "IL STUDENT ASSISTANCE COMM" ,
           #  Category == "900" ~ "NOT IN FRAME",
             Category == "901" ~ "State Pension Contribution",
             Category == "903" ~ "Debt Service",
             Category == "904" ~ "State Employee Healthcare",
           #  Category == "910" ~ "LEGISLATIVE"  ,
          #   Category == "920" ~ "JUDICIAL" ,
         #   Category == "930" ~ "Elected Officers" , 
            # Category == "940" ~ "OTHER HEALTH-RELATED", 
             Category == "941" ~ "Public Safety" ,
           #  Category == "942" ~ "ECON DEVT & INFRASTRUCTURE" ,
           #  Category == "943" ~ "CENTRAL SERVICES",
           #  Category == "944" ~ "BUS & PROFESSION REGULATION" ,
             Category == "945" ~ "Medicaid" ,
          #   Category == "946" ~ "Capital Improvement" , 
           #  Category == "948" ~ "OTHER DEPARTMENTS" ,
            # Category == "949" ~ "OTHER BOARDS & COMMISSIONS" ,
             Category == "959" ~ "K-12 Education" ,
             Category == "960" ~ "University Education",
             Category == "970" ~ "Local Govt Revenue Sharing",
          Category == "TOTALS" ~ "Total Expenditures",
             T ~ "All Other Expenditures **")
           ) %>% 
  select(-type, -Category) %>% 
  group_by(Year, Category_name) %>% 
  summarise(Dollars = sum(Dollars))

expenditure_wide_majorcats <- exp_long_majorcats %>% 
  pivot_wider(names_from = Category_name, 
              values_from = Dollars) %>%
  relocate("All Other Expenditures **", .after = last_col()) %>%
  relocate("Total Expenditures", .after =  last_col())


# CAGR values for largest expenditure categories and combined All Other Expenditures

# function for calculating the CAGR
calc_cagr <- function(df, n) {
  df <- exp_long_majorcats %>%
    #select(-type) %>%
    arrange(Category_name, Year) %>%
    group_by(Category_name) %>%
    mutate(cagr = ((`Dollars` / lag(`Dollars`, n)) ^ (1 / n)) - 1)

  return(df)
}

# This works for one variable at a time
cagr_25 <- calc_cagr(exp_long_majorcats, 25) %>% 
  # group_by(Category) %>%
  summarize(cagr_25 = sum(cagr*100, na.rm = TRUE))

cagr23_precovid <- exp_long_majorcats %>%
  filter(Year <= 2019) %>%
  calc_cagr(21) %>% 
  summarize(cagr_21 = sum(cagr*100, na.rm = TRUE))



cagr_10 <- calc_cagr(exp_long_majorcats, 10) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_10 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_5 <- calc_cagr(exp_long_majorcats, 5) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_5 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_3 <- calc_cagr(exp_long_majorcats, 3) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_3 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_2 <- calc_cagr(exp_long_majorcats, 2) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_2 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_1 <- calc_cagr(exp_long_majorcats, 1) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_1 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

CAGR_expenditures_majorcats_tot <- data.frame(cagr_1, cagr_2, cagr_3, cagr_5, cagr_10, cagr_25 ) %>% 
  select(-c(Category_name.1, Category_name.2, Category_name.3, Category_name.4, Category_name.5 )) %>% 
  rename("Expenditure Category" = Category_name, "1 Year CAGR" = cagr_1, "2 Year CAGR" = cagr_2, "3 Year CAGR" = cagr_3, "5 Year CAGR" = cagr_5, "10 Year CAGR" = cagr_10,"25-Year CAGR" = cagr_25 )

move_to_last <- function(df, n) df[c(setdiff(seq_len(nrow(df)), n), n), ]

CAGR_expenditures_majorcats_tot <- move_to_last(CAGR_expenditures_majorcats_tot, 1)
CAGR_expenditures_majorcats_tot <- move_to_last(CAGR_expenditures_majorcats_tot, 14) 


CAGR_expenditures_majorcats_tot%>%   
  kbl(caption = "CAGR Calculations for Largest Expenditure Categories" , row.names=FALSE) %>% 
     kable_classic()  %>%
    row_spec(17, bold = T, color = "black", background = "gray")
current_year <- 2023
last_year <- 2022

# Yearly change for Top 13 largest expenditure categories
expenditure_change_majorcats <- exp_long_majorcats %>%
  filter(Year > 2021) %>%
  pivot_wider(names_from = Year , values_from = Dollars,   names_prefix = "Dollars_") %>%
  mutate("FY 2023 ($ Billions)" = Dollars_2023/1000,
         "FY 2022 ($ Billions)" = Dollars_2022/1000,
         "1-Year Change" = percent((Dollars_2023 -Dollars_2022)/Dollars_2022, accuracy = .1) )  %>%
  left_join(CAGR_expenditures_majorcats_tot, by = c("Category_name" = "Expenditure Category")) %>% 
  arrange(-`FY 2023 ($ Billions)`)%>%
  mutate(`25-Year CAGR` = percent(`25-Year CAGR`/100, accuracy=.1)) %>%
  select(-c(Dollars_2023, Dollars_2022, `1 Year CAGR`:`10 Year CAGR`)) %>%
  rename( "25-Year CAGR" = `25-Year CAGR`, 
          "Expenditure Category" = Category_name )

expenditure_change_majorcats <- move_to_last(expenditure_change_majorcats, 4) 

expenditure_change_majorcats <- move_to_last(expenditure_change_majorcats, 1)

expenditure_change_majorcats %>% 
  kbl(caption = "Yearly Change in Expenditures", row.names = FALSE, align = "l") %>% 
  kable_classic() %>%
    row_spec(17, bold = T, color = "black", background = "gray")
Table 3.3: Largest Expenditure Categories with CAGRs
CAGR Calculations for Largest Expenditure Categories
Expenditure Category 1 Year CAGR 2 Year CAGR 3 Year CAGR 5 Year CAGR 10 Year CAGR 25-Year CAGR
Children & Family Services 25.9859009 10.0344085 8.4022688 7.779575 3.3335986 0.7766504
Community Development 7.7125186 -5.4550293 34.1826333 24.627482 4.3415932 4.7917662
Corrections 13.4597177 2.7976240 3.4710988 -2.649786 3.1274136 2.2089231
Debt Service -2.8871960 -1.8637196 0.0719999 -0.196211 0.7950780 5.7307260
Human Services 21.3386342 16.1025281 13.7662650 10.186506 4.6360395 3.2865014
K-12 Education 9.8602395 9.8635931 9.1469312 7.039479 5.3528615 4.3478887
Local Govt Revenue Sharing 5.3563005 23.2327665 19.0836206 12.017295 6.4612497 4.6775563
Medicaid 13.1042564 11.0519496 13.2863258 11.759034 8.1698752 7.4322089
Public Safety 1.1798222 -6.8720189 5.3743040 14.124412 7.5811865 5.6904569
Revenue 66.6779115 34.8510374 39.1559319 41.418636 22.0196955 8.1401232
State Employee Healthcare -0.0947205 2.0774249 0.2291473 -12.494408 3.2765846 5.8208663
State Pension Contribution 5.0581029 10.1158161 8.8529994 9.544734 9.0593992 10.5213967
Tollway -9.8915774 -1.7810457 -0.4137358 5.234151 7.1403223 6.7795355
Transportation 15.8132934 -0.8141654 9.0562698 8.023147 2.1131622 4.0178977
University Education 12.5185792 7.4953331 5.0065012 4.602954 0.9849009 0.8145729
All Other Expenditures ** 14.3632442 6.6156440 8.0188779 6.784831 4.1574214 5.6602079
Total Expenditures 11.8967130 9.7938493 11.1216626 8.353445 5.8718080 5.2384823
Yearly Change in Expenditures
Expenditure Category FY 2023 ($ Billions) FY 2022 ($ Billions) 1-Year Change 25-Year CAGR
Medicaid 32.406413 28.651806 13.1% 7.4%
K-12 Education 14.697912 13.378737 9.9% 4.3%
Local Govt Revenue Sharing 10.901300 10.347080 5.4% 4.7%
Human Services 8.829551 7.276784 21.3% 3.3%
State Pension Contribution 6.818033 6.489774 5.1% 10.5%
Transportation 5.294994 4.572009 15.8% 4.0%
Revenue 3.113081 1.867723 66.7% 8.1%
State Employee Healthcare 3.002363 3.005209 -0.1% 5.8%
University Education 2.549872 2.266179 12.5% 0.8%
Debt Service 1.951437 2.009453 -2.9% 5.7%
Tollway 1.893930 2.101834 -9.9% 6.8%
Public Safety 1.741655 1.721346 1.2% 5.7%
Corrections 1.704290 1.502111 13.5% 2.2%
Children & Family Services 1.577760 1.252331 26.0% 0.8%
Community Development 1.498420 1.391129 7.7% 4.8%
All Other Expenditures ** 13.992969 12.235548 14.4% 5.7%
Total Expenditures 111.973980 100.069052 11.9% 5.2%

Top 10 revenue sources CAGRs and Yearly Change Tables:

Code
##### Top 10 revenue CAGRs: ####


calc_cagr <- function(df, n) {
  df <- rev_long_majorcats %>%
    arrange(Category_name, Year) %>%
    group_by(Category_name) %>%
    mutate(cagr = ((Dollars / lag(Dollars, n)) ^ (1 / n)) - 1)

  return(df)
}

# This works for one variable at a time
cagr_25 <- calc_cagr(rev_long_majorcats, 25) %>% 
     # group_by(Category) %>%
  summarize(cagr_25 = sum(cagr*100, na.rm = TRUE))

cagr_10 <- calc_cagr(rev_long_majorcats, 10) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_10 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_5 <- calc_cagr(rev_long_majorcats, 5) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_5 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_3 <- calc_cagr(rev_long_majorcats, 3) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_3 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

cagr_2 <- calc_cagr(rev_long_majorcats, 2) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_2 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

 cagr_1 <- calc_cagr(rev_long_majorcats, 1) %>% 
  filter(Year == 2023) %>%
  summarize(cagr_1 = case_when(Year == 2023 ~ sum(cagr*100, na.rm = TRUE)))

CAGR_revenue_majorcats_tot <- data.frame(cagr_1, cagr_2, cagr_3, cagr_5, cagr_10, cagr_25) %>%   
  select(-c(Category_name.1, Category_name.2, Category_name.3, Category_name.4, Category_name.5 )) %>% 
  rename("Revenue Category" = Category_name, "1 Year CAGR" = cagr_1, "2 Year CAGR" = cagr_2, "3 Year CAGR" = cagr_3, "5 Year CAGR" = cagr_5, "10 Year CAGR" = cagr_10, "25-Year CAGR" = cagr_25 )

CAGR_revenue_majorcats_tot <- move_to_last(CAGR_revenue_majorcats_tot,1)
CAGR_revenue_majorcats_tot <- move_to_last(CAGR_revenue_majorcats_tot,11)

CAGR_revenue_majorcats_tot %>% 
  kbl(caption = "CAGR Calculations for Largest Revenue Sources", row.names = FALSE) %>% 
  kable_classic() %>%
    row_spec(12, bold = T, color = "black", background = "gray")
Table 3.4: Top 10 Revenue Sources with CAGRs
CAGR Calculations for Largest Revenue Sources
Revenue Category 1 Year CAGR 2 Year CAGR 3 Year CAGR 5 Year CAGR 10 Year CAGR 25-Year CAGR
Corporate Income Tax 4.556312 32.278053 42.323578 24.8790961 8.951797 7.161330
Federal Medicaid Reimbursements 6.091446 7.281008 13.437313 8.4399008 9.323655 7.466601
Federal Other -43.869229 8.684250 3.878481 13.3947814 5.666730 4.354733
Federal Transportation 15.300213 -5.742851 5.831739 5.8205519 2.173993 3.784926
Income Tax -4.280389 2.735038 8.894055 6.1856097 4.346622 5.368743
Licenses, Fees, Registration 9.246722 2.238833 13.234254 11.0722031 6.180345 7.944139
Medical Provider Assessments 9.497774 3.597628 5.579728 13.1480627 9.456415 8.408949
Motor Fuel Taxes 1.656423 3.819450 3.475268 13.4350273 7.386331 2.760353
Receipts from Revenue Producing 8.470365 5.703997 5.994509 0.8918354 2.687535 5.207634
Sales Tax 4.835811 8.040426 9.745200 6.6729036 4.712900 3.303547
All Other Sources ** 14.470649 14.127358 13.756850 6.8896943 4.753734 4.298007
Total Revenue -3.693761 8.352330 11.520802 8.8896256 5.883931 5.126247
Code
###### Yearly change summary table for Top 10 Revenues #####
revenue_change_majorcats <- rev_long_majorcats %>%
  #select(-c(Category)) %>%
  filter(Year > 2021) %>%
  pivot_wider(names_from = Year , values_from = Dollars,   names_prefix = "Dollars_") %>%
  mutate(
    "FY 2023 ($ billions)" = Dollars_2023/1000,
            "FY 2022 ($ billions)" = Dollars_2022/1000,

         "1-Year Change" = percent(((Dollars_2023 -Dollars_2022)/Dollars_2022), accuracy = .1)) %>%
  left_join(CAGR_revenue_majorcats_tot, by = c("Category_name" = "Revenue Category")) %>% 
    arrange(-`FY 2023 ($ billions)`)%>%
  #select(-c(Dollars_2021, Dollars_2021, `1 Year CAGR`:`10 Year CAGR`)) %>%
  mutate("25-Year Change" = percent(`25-Year CAGR`/100, accuracy=.1)) %>%
  rename("Revenue Category" = Category_name ) %>%
  select(-c(Dollars_2023, Dollars_2022, `1 Year CAGR`:`25-Year CAGR`)) 

revenue_change_majorcats <- move_to_last(revenue_change_majorcats,5)

revenue_change_majorcats <- move_to_last(revenue_change_majorcats,1)

revenue_change_majorcats%>% 
  kbl(caption = "Yearly Change in Revenue for Main Revenue Sources", row.names = FALSE, align = "l") %>% 
   kable_classic() %>%
    row_spec(12, bold = T, color = "black", background = "gray")
Table 3.5: Top 10 Revenue Sources with CAGRs
Yearly Change in Revenue for Main Revenue Sources
Revenue Category FY 2023 ($ billions) FY 2022 ($ billions) 1-Year Change 25-Year Change
Income Tax 25.309542 26.441334 -4.3% 5.4%
Federal Medicaid Reimbursements 20.205138 19.045021 6.1% 7.5%
Sales Tax 16.222466 15.474165 4.8% 3.3%
Federal Other 10.878720 19.381027 -43.9% 4.4%
Corporate Income Tax 10.484327 10.027445 4.6% 7.2%
Medical Provider Assessments 4.088380 3.733756 9.5% 8.4%
Receipts from Revenue Producing 2.589099 2.386919 8.5% 5.2%
Motor Fuel Taxes 2.569229 2.527365 1.7% 2.8%
Federal Transportation 2.114102 1.833563 15.3% 3.8%
Licenses, Fees, Registration 2.060599 1.886189 9.2% 7.9%
All Other Sources ** 15.252721 13.324569 14.5% 4.3%
Total Revenue 111.774323 116.061352 -3.7% 5.1%

Export summary file with Totals

Code
#install.packages("openxlsx")
library(openxlsx)

dataset_names <- list('Aggregate Revenues' = revenue_wide2, 
                      'Aggregate Expenditures' = expenditure_wide2, 

                      
                      'Table 1' = revenue_change_majorcats, #Top categories with yearly change, 23 yr cagr
                      'Table 2' = expenditure_change_majorcats,
                      
                      'Table 1a. AllCats' = revenue_change2,
                      'Table 2a. AllCats' = expenditure_change2,
                      
                      'CAGR Rev-MajorCats' = CAGR_revenue_majorcats_tot, # Categories Match Table 1 in paper
                      'CAGR Exp-MajorCats' = CAGR_expenditures_majorcats_tot, 
                                            
                     # 'Table 1-AllCats' = expenditure_change_allcats,  # All Categories by Year
                    #  'Table 2-AllCats' = revenue_change_allcats,
                      
                    #  'CAGR_Revenue-AllCats' = CAGR_revenue_summary_tot, 
                   #   'CAGR_Expenditures-AllCats' = CAGR_expenditures_summary_tot, 
                      
                      'Fiscal Gap' = year_totals,    # Total Revenue, Expenditure, and Fiscal gap per year
                      
                      'aggregated_totals_long' = aggregated_totals_long # all data in long format. Good for creating pivot tables in Excel
                      )

write.xlsx(dataset_names, file = 'summary_file_FY23_wTotals_22March2024.xlsx')

Saves main items in one excel file named summary_file.xlsx. Delete eval=FALSE to run on local computer.