knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)
library(tidyverse)
library(DBI)
library(data.table)
library(ggspatial)
library(gstat)
library(here)
library(httr)
library(jsonlite)
library(ptaxsim)
library(sf)
library(stars)
library(glue)
# Create the DB connection with the default name expected by PTAXSIM functions
ptaxsim_db_conn <- DBI::dbConnect(RSQLite::SQLite(), "./ptaxsim.db/ptaxsim-2021.0.4.db")
# has all potential property classes for pins
# downloaded from CCAO gitlab website
## I used this to merge additional information to the pins and class data later on.
class_dict <- read_csv("class_dict.csv")
options(digits=4, scipen = 999)
Cook County Assessors Data description page and other datasets.
Pull all agency names that exist, then use agency numbers associated
with MUNI types to pull only the muni_agency_names
object.
There are 946 unique taxing agencies that existed in 2021.
# has EAV values, extensions by agency_num
agency_dt <- DBI::dbGetQuery(
ptaxsim_db_conn,
"SELECT *
FROM agency
WHERE year = 2021
"
)
# show variables that exist in the data table
agency_dt
This table is used for the municipality levy in later calculations.
agency_dt has all taxing agencies (BUT NOT TIFS!) that
existed in 2021 and includes their total taxable base (cty_cook_eav),
their levy, taxing rate, binary variables for if a municipality is home
rule or not, as well as many other variables. Also currently doesn’t
have agency_names to go with the agency numbers. That will be merged in
shortly.
The table has waaaay too many columns to display all at once.Click the arrow on the right-hand side of the table to see additional columns that are hidden initially in the display.
# grabs all unique muni names. Would be needed if creating a loop for calculating all munis
# municipality names and their agency number
muni_agency_names <- DBI::dbGetQuery(
ptaxsim_db_conn,
"SELECT DISTINCT agency_num, agency_name, minor_type
FROM agency_info
WHERE minor_type = 'MUNI'
OR agency_num = '020060000'
"
)
muni_agency_names <- muni_agency_names %>%
mutate(first6 = str_sub(agency_num,1,6),
first5 = str_sub(agency_num,1,5)) %>%
select(-minor_type)
Makes a list of ALL taxing agencies, including TIFs, SSAs, etc.
First 5 and 6 digit codes variables are created to nest TIFs and other taxing agencies within the municipality. Still experimental, variables are not used in the code below.
There are 1,878 taxing agencies. When grouped by minor_type, there are 133 muni agencies, 639 tif agencies, 30 townships, etc.
# all agency names, numbers, and types
# includes TIF and non-TIF agencies
all_taxing_agencies <- DBI::dbGetQuery(
ptaxsim_db_conn,
"SELECT agency_num, agency_name, major_type, minor_type
FROM agency_info
"
)
# create same first 5 and 6 digit codes
all_taxing_agencies <- all_taxing_agencies %>%
mutate(first6 = str_sub(agency_num,1,6),
first5 = str_sub(agency_num,1,5))
all_taxing_agencies
# 133 muni agencies, 639 tif agencies, 30 townships, etc.
# we use the 133 muni numbers and Cicero's agency number to get the 134 municipalities.
table(all_taxing_agencies$minor_type)
##
## BOND COMM COLL COOK ELEMENTARY FIRE GEN ASST HEALTH
## 16 11 3 118 39 31 13
## INFRA LIBRARY MISC MOSQUITO MUNI PARK POLICE
## 25 111 14 4 133 102 4
## SANITARY SECONDARY SSA TIF TOWNSHIP UNIFIED WATER
## 18 29 529 639 30 6 3
table(all_taxing_agencies$major_type)
##
## COOK COUNTY MISCELLANEOUS MUNICIPALITY/TOWNSHIP
## 3 257 1451
## SCHOOL
## 167
All agencies have an agency name, but there isn’t an indicator for
which municipality each taxing agency is within. Merging the
muni_agency_names to all_taxing_agencies
hopefully adds a variable that allows for grouping and summing at the
municipality level that includes SSAs, (if we want to do that). Does not
work for School districts or other taxing districts which begin with
completely different values (e.g. school districts begin with 4).
Everything depends on the tax codes.
# none of these are included in their geographic municipality areas automatically. have different agency number code beginnings too.
all_taxing_agencies %>%
filter(major_type == "MISCELLANEOUS") %>%
group_by(minor_type) %>%
summarize(count = n())
#all_taxing_agencies %>% filter(minor_type == "FIRE")
Add agency names since those weren’t included originally in the table:
all_taxing_agencies <- all_taxing_agencies %>%
left_join(muni_agency_names, by = c("first5", "first6")) %>%
rename(muni_name = agency_name.y,
muni_num = agency_num.y,
agency_name = agency_name.x,
agency_num = agency_num.x)
# combine taxing agency names and agency type to data table that has eav and extension values
agency_data <- right_join(agency_dt, all_taxing_agencies) %>%
# get rid of unneeded columns to make table outputs smaller
select(-c(cty_dupage_eav:cty_livingston_eav, lim_numerator, lim_denominator)) %>% # drop some of the unused variables
arrange(first5, agency_num)
Using the agency numbers for each municipality, I pull all tax codes that have an agency_num included in the muni_agency_names object. By narrowing the agencies down to just Municipality types, this prevents duplicate tax_codes from being pulled.
There are 3774 tax codes within Cook County that are taxed by Municipalities. There are 4228 unique tax codes in Cook County in 2021.
# muni_agency_nums<- all_taxing_agencies %>%
# filter(minor_type %in% c("MUNI")) %>%
# select(agency_num)
muni_tax_codes <- DBI::dbGetQuery(
ptaxsim_db_conn,
glue_sql("
SELECT*
FROM tax_code
WHERE agency_num IN ({muni_agency_names$agency_num*})
AND year = 2021
",
.con = ptaxsim_db_conn
)
)
# There are 4228 unique taxcodes in Cook County in 2021
cook_tax_codes <- DBI::dbGetQuery(
ptaxsim_db_conn,
glue_sql("
SELECT *
FROM tax_code
WHERE year = 2021
AND agency_num = '010010000'
",
.con = ptaxsim_db_conn
)
)
Levy data from agency_data is broken down by each taxing
agency. Calculating a municipal levy can’t be done from this table
because you can’t get a composite tax rate or the amount of eav within a
region that is being taxed by taxing agency. But this does have the
total levy and total eav of all taxing districts.
In later steps, the levy is actually calculated from the
final_tax_to_dist variable from the tax_bill() output for
all pins.
muni_levies <- agency_data %>%
filter(minor_type == "MUNI" |
agency_num == "020060000" # Cicero is an oddball and considered Township
)
# variables that exist for the 134 municipalities for levies and extensions values
# originally from agency dt
muni_levies
I find all TIF taxing agencies and tax codes that are taxed by TIF agencies.
TFI_distrib table total EAV within each tax code, the frozen EAV amount, tax code rate, and tax code revenue variables. Super helpful table, however, I don’t directly use the table at this point in time.
# Agency number and agency name for all TIFs
TIF_agencies <- DBI::dbGetQuery(
ptaxsim_db_conn,
"SELECT DISTINCT agency_num, agency_name, major_type, minor_type
FROM agency_info
WHERE minor_type = 'TIF'
"
)
unique_tif_taxcodes <- DBI::dbGetQuery(
ptaxsim_db_conn,
glue_sql("
SELECT DISTINCT tax_code_num
FROM tax_code
WHERE agency_num IN ({TIF_agencies$agency_num*})
AND year = 2021
",
.con = ptaxsim_db_conn
)
)
tif_distrib <- DBI::dbGetQuery(
ptaxsim_db_conn,
glue_sql("
SELECT *
FROM tif_distribution
WHERE tax_code_num IN ({muni_tax_codes$tax_code_num*})
AND year = 2021
",
.con = ptaxsim_db_conn
)
)
tif_distrib
#frozen_eav <- tif_distrib %>% select(year, tax_code_num, tax_code_eav, tax_code_frozen_eav)
Using the TIF distribution tables and tax_code tables included in ptaxsim, I calculate the total revenue collected by each tax code (and the amount that went to TIFs or the “district”.)
tif_distrib has the frozen eav within each tax_code, eav
of the tax code, and the total revenue collected. This table is
incorporated into the ptaxsim tax_bill() output when using ptaxsim
correctly. I do not directly use this table in steps below.
tif_distrib <- tif_distrib %>%
mutate(#first6= str_sub(agency_num,1,6),
# first5 = str_sub(agency_num, 1,5),
tif_increment = tax_code_eav - tax_code_frozen_eav,
total_taxcode_revenue = tax_code_eav*tax_code_rate/100,
district_revenue_perTC = tax_code_frozen_eav*tax_code_rate/100,
# checking to see if equal to tax_code_revenue
# same but has the negative revenue values instead of zeros.
tif_revenue_perTC = total_taxcode_revenue - district_revenue_perTC)
# add
#tif_distrib <- tif_distrib %>% left_join(muni_tax_codes)
tif_distrib
There are 1,825,816 pins in 2021 that are taxed by Municipalities.
There are 1,864,594 pins taxed by Cook County in 2021.
Difference is 38,779 pins. Initial hypothesis: these are in unincorporated areas?
muni_pins <- DBI::dbGetQuery(
ptaxsim_db_conn,
glue_sql(
"SELECT DISTINCT pin, class, tax_code_num
FROM pin
WHERE tax_code_num IN ({muni_tax_codes$tax_code_num*})
AND year = 2021
",
.con = ptaxsim_db_conn
))
# There are 1,864,594 pins taxed by Cook County in 2021.
cook_pins <- DBI::dbGetQuery(
ptaxsim_db_conn,
glue_sql(
"SELECT DISTINCT pin, class, tax_code_num
FROM pin
WHERE tax_code_num IN ({cook_tax_codes$tax_code_num*})
AND year = 2021
",
.con = ptaxsim_db_conn
))
# change variable type to character so the join works.
class_dict$class_code <- as.character(class_dict$class_code)
# finds all pins within a municipality
pin_data <- lookup_pin(2021, cook_pins$pin) %>%
left_join(cook_pins, by = c("pin", "class"))
# use the property class to make the major property types
# joins the class_dict file to the pin_data classes
pin_data <- class_dict %>%
select(-c(assessment_level:reporting_group, class_desc:max_size)) %>%
right_join(pin_data, by = c("class_code" = "class"))
write_csv(pin_data, "all_cook_pins_2021.csv")
exemptions_inCook_perTC <- pin_data %>%
group_by(tax_code_num, major_class_code, major_class_type) %>%
summarize(eav=sum(eav, na.rm=TRUE),
exe_homeowner = sum(exe_homeowner, na.rm=TRUE),
exe_senior = sum(exe_senior, na.rm=TRUE),
exe_freeze = sum(exe_freeze, na.rm=TRUE),
exe_longtime_homeowner = sum(exe_longtime_homeowner, na.rm=TRUE),
exe_disabled = sum(exe_disabled, na.rm=TRUE),
exe_vet_returning = sum(exe_vet_returning, na.rm=TRUE),
exe_vet_dis = sum(exe_vet_dis_lt50 + exe_vet_dis_50_69 + exe_vet_dis_ge70, na.rm=TRUE),
exe_abate = sum(exe_abate, na.rm=TRUE),
pin_count = n() # number of pins within each tax code and property combo
)
write_csv(exemptions_inCook_perTC, "all_exemptions_inCook_byTaxcode.csv")
# change variable type to character so the join works.
class_dict$class_code <- as.character(class_dict$class_code)
# finds all pins within a municipality
pin_data <- lookup_pin(2021, muni_pins$pin) %>%
left_join(muni_pins, by = c("pin", "class"))
# use the property class to make the major property types
# joins the class_dict file to the pin_data classes
pin_data <- class_dict %>%
select(-c(assessment_level:reporting_group, class_desc:max_size)) %>%
right_join(pin_data, by = c("class_code" = "class"))
write_csv(pin_data, "all_muni_pins_2021.csv")
exemptions_inMunis_perTC <- pin_data %>%
group_by(tax_code_num, major_class_code, major_class_type) %>%
summarize(eav=sum(eav, na.rm=TRUE),
exe_homeowner = sum(exe_homeowner, na.rm=TRUE),
exe_senior = sum(exe_senior, na.rm=TRUE),
exe_freeze = sum(exe_freeze, na.rm=TRUE),
exe_longtime_homeowner = sum(exe_longtime_homeowner, na.rm=TRUE),
exe_disabled = sum(exe_disabled, na.rm=TRUE),
exe_vet_returning = sum(exe_vet_returning, na.rm=TRUE),
exe_vet_dis = sum(exe_vet_dis_lt50 + exe_vet_dis_50_69 + exe_vet_dis_ge70, na.rm=TRUE),
exe_abate = sum(exe_abate, na.rm=TRUE),
pin_count = n() # number of pins within each tax code and property combo
)
write_csv(exemptions_inMunis_perTC, "all_exemptions_inMunis_byTaxcode.csv")
I have not calculated separate taxbill summaries using the Cook pins instead of the Muni pins. Sticking with muni pins until time to explore the differences - AWM 6.29.2023
The tax_bill() command returns information for each taxing agency that taxed a pin. EACH pin can be taxed by 15+ taxing agencies and each taxing agency has its own taxing rate.
Each tax code has a unique combination of taxing agencies taxing properties within in. So, each tax code has a summed EAV value from each pin within it and a “composite tax rate” made from the taxing agencies and their taxable base. In the code below, its called the weighted tax rate but that isn’t the best name for it… It is more like the amount of money that each taxing agency gets from each property.
revenue collected(previously named weighted_tax_rate in
earlier code versions) was calculated for every tax bill observation for
every pin and is equal to the eav of a property X agency_tax_rate. Then
all pins within tax codes were summed together to get a tax code level
EAV and tax code level tax rate. Note: There is a tax_code_rate
variable included in the muni_tax_codes object made from
the tax_code dt included in ptaxsim
Found code issue: had mutate(frozen_eav = ifelse(in_TIF =1, final_tax_to_dist)) which was returning the revenue collected from the frozen eav, not the frozen eav. AAGGHHH. Glad I caught it. Doesn’t change large picture of EAV or burden.
# creates the 4.5 GB file of all taxbills for 2021.
bills <- tax_bill(2021, muni_pins$pin, simplify = FALSE)
bills_byClass_pertaxcode <- bills %>%
left_join(class_dict, by = c("class" = "class_code")) %>% # add major property types to pin data
mutate(revenue_collected_pre_exemp = agency_tax_rate*eav) %>% # $ that each taxing agency gets from each pin
mutate(revenue_collected_post_exemp = agency_tax_rate*(eav-exe_total) )%>% # $ that each taxing agency gets from each pin
group_by(major_class_code, major_class_type, tax_code) %>%
summarize(#tax_code_rate = first(tax_code_rate),
final_tax_to_dist = sum(final_tax_to_dist),
final_tax_to_tif = sum(final_tax_to_tif),
tax_amt_exe = sum(tax_amt_exe), # revenue lost due to exemptions
tax_amt_pre_exe = sum(tax_amt_pre_exe), # total rev before exemptions
tax_amt_post_exe = sum(tax_amt_post_exe), # total rev after exemptions
rpm_tif_to_cps = sum(rpm_tif_to_cps), # not used
rpm_tif_to_rpm = sum(rpm_tif_to_rpm), # not used
rpm_tif_to_dist = sum(rpm_tif_to_dist), # not used
tif_share = mean(tif_share), # not used
rev_perTC_pre_exemp = sum(revenue_collected_pre_exemp),
rev_perTC_post_exemp = sum(revenue_collected_post_exemp)) %>%
left_join(all_tax_codes, by = c("tax_code" = "tax_code_num"))
# na.rm = TRUE option is probably only needed for calculating the mean values of variables. I just didn't delete it yet. Avoids getting an error if there is a zero when calculating mean.
#write_csv(bills, "all_taxbills_inMunis.csv")
## adds additional variables for frozen eav from tif_distrib table!
# if the tax code is not a TIF, then valus will all be NA
bills_byClass_pertaxcode2 <- bills_byClass_pertaxcode %>%
select(-c(agency_num, agency_rate)) %>%
left_join(tif_distrib, by = c("tax_code" = "tax_code_num", "tax_code_rate", "year")) %>%
select(-c(agency_num, agency_num))
write_csv(bills_byClass_pertaxcode2, "taxbills_inMunis_perTC.csv")
# Looking at properties taxbills that have NA as a property type:
bills_NA_propertytypes <- bills %>%
left_join(class_dict, by = c("class" = "class_code")) %>%
filter(major_class_code == NA)
write_csv(bills_NA_propertytypes, "taxbills_with_no_PropertyClass.csv")
bills_propertyclass_counts <- bills %>%
# left_join(class_dict, by = c("class" = "class_code")) %>%
group_by(class) %>%
summarize(property_count = n())
write_csv(bills_propertyclass_counts, "bills_propertyclass_counts.csv")
Tax bill data was pulled from ptaxsim using the tax_bill() function for all pins taxed by the 134 Municipalities within Cook County in 2021. This was a 4.5 GB file. In order to make the file manageable and combine it with exemption data for all municipalities, I summarized values to the TaxCode-PropertyClass level. This is later summarized by municipalities by aggregating the taxcodes that are taxed by each municipality.
Tax bills grouped and summarized for each tax code’s property class types make 9716 observations. (3774 tax codes X multiple property types in every tax code).
taxbills_by_Class_per_TC <- read_csv("taxbills_inMunis_perTC.csv") %>%
mutate(tax_code = as.character(tax_code)) %>%
# left_join(muni_tax_codes, by = c("tax_code" = "tax_code_num")) %>%
mutate(#final_tax_to_dist = ifelse(is.na(final_tax_to_dist), 0, final_tax_to_dist),
TaxableEAV = (final_tax_to_dist/(tax_code_rate/100)),
TaxableEAV_b4_exemps = ((final_tax_to_dist+tax_amt_exe)/(tax_code_rate/100)),
TIF_increment = (final_tax_to_tif/(tax_code_rate/100)),
TotalEAV_post_exe = (tax_amt_post_exe/(tax_code_rate/100)),
TotalEAV_pre_exe = (tax_amt_pre_exe/(tax_code_rate/100))
)
#taxbills_by_Class_per_TC <- left_join(taxbills_by_Class_per_TC , muni_agency_names)
taxbills_by_Class_per_TC
taxbills_by_Class_per_TC %>% mutate(
"Current Tax Revenue(District+TIF)" = scales::dollar(tax_amt_post_exe),
"District Revenue" = scales::dollar(final_tax_to_dist),
"TIF Revenue" = scales::dollar(final_tax_to_tif),
"Lost Revenue from Exempt." = scales::dollar(tax_amt_exe), # same as tax_amt_pre_exe-tax_amt_post_exe
# "Revenue Collected" = rev_perTC, # within each Taxcode
) %>%
# mutate(`EAV in and out of TIFs` = `EAV in TIF (Increment)`+`EAV outside TIF`,
# EAVbeforeExemptions = `EAV in and out of TIFs`+Exemptions) %>%
select(-c(tax_amt_post_exe, final_tax_to_dist:tax_amt_pre_exe,
#first5, first6, rev_perTC, agency_tax_rate,
year, rpm_tif_to_cps:rpm_tif_to_dist, tif_share, major_class_code))
#class_exemptions <- full_join(exemptions_by_class, class_eav, by = "major_class_code") %>%
# select(major_class_code, major_class_type, eav, final_tax_to_dist, tax_share)
#class_exemptions
Uses exemption data from the lookup_pin() command for all pins in Cook County in 2021. Values are then summarized by Tax codes and property types (so each Municipality has a lot of rows at this point BUT this stage of the data is the most flexible for calculations and future research questions.)
exemptions_by_class_per_TC <- read_csv("all_exemptions_by_TC.csv") %>%
mutate(tax_code_num = as.character(tax_code_num))
Combine the tax bill data with the exemption data for every tax code and property class combination:
taxcode_data <- full_join(exemptions_by_class_per_TC, taxbills_by_Class_per_TC,
by = c("tax_code_num" = "tax_code", "major_class_code", "major_class_type")) %>%
left_join(muni_tax_codes) %>% select(-agency_rate)
MuniLevy <- taxcode_data %>%
group_by(agency_num)%>%
summarize(MuniLevy = round(sum(final_tax_to_dist)),
TIF_rev_perMuni = sum(final_tax_to_tif),
Lostrev_fromExemps_perMuni = sum(tax_amt_exe),
Levy_plus_TIFrev_perMuni =round(sum(final_tax_to_dist + final_tax_to_tif)),
#taxableEAV includes EAV outside of TIFs and frozen EAV in TIFs
TaxableEAV_perMuni = sum(final_tax_to_dist/(tax_code_rate/100)), # MuniLevy / tax rate
TIF_increment_perMuni = sum(final_tax_to_tif/(tax_code_rate/100)), # Tif revenue / tax rate
TotalEAV_pre_exe_perMuni = sum(tax_amt_pre_exe/(tax_code_rate/100)), # includes TIF and district revenue!
TotalEAV_post_exe_perMuni = sum(tax_amt_post_exe/(tax_code_rate/100)), # includes TIF and district revenue!
# TotalEAV_post_exe2 = sum((final_tax_to_dist + final_tax_to_tif)/(tax_code_rate/100)) # same as above
) %>%
mutate(Exempted_EAV_perMuni = TotalEAV_pre_exe_perMuni - TotalEAV_post_exe_perMuni,
TaxableEAV_b4_exemps_perMuni = TaxableEAV_perMuni + Exempted_EAV_perMuni,
TotalRevenueCollected_perMuni = Lostrev_fromExemps_perMuni+Levy_plus_TIFrev_perMuni,
taxrate_current = MuniLevy/TaxableEAV_perMuni,
taxrate_noexemps = MuniLevy/TaxableEAV_b4_exemps_perMuni,
inbetweenstep_rev_perMuni = taxrate_noexemps*TaxableEAV_perMuni,
taxrate_change = taxrate_current - taxrate_noexemps) %>%
left_join(muni_agency_names)
taxcode_data <- left_join(taxcode_data, MuniLevy)
#, "TaxableEAV", "TIF_increment","TotalEAV_post_exe","TotalEAV_pre_exe"))
write.csv(taxcode_data, "tax_codes_all_merged_data.csv")
# # all munis + Cicero
# MuniLevy <- taxbills_by_Class_per_TC %>%
# group_by(agency_name)%>%
# summarize(MuniLevy = round(sum(final_tax_to_dist)),
# TIF_rev_perMuni = sum(final_tax_to_tif),
# Lostrev_fromExemps_perMuni = sum(tax_amt_exe),
# Levy_plus_TIFrev_perMuni =round(sum(final_tax_to_dist + final_tax_to_tif)),
# #taxableEAV includes EAV outside of TIFs and frozen EAV in TIFs
# TaxableEAV_perMuni = sum(final_tax_to_dist/(tax_code_rate/100)), # MuniLevy / tax rate
# TIF_increment_perMuni = sum(final_tax_to_tif/(tax_code_rate/100)), # Tif revenue / tax rate
# TotalEAV_pre_exe_perMuni = sum(tax_amt_pre_exe/(tax_code_rate/100)), # includes TIF and district revenue!
#
# TotalEAV_post_exe_perMuni = sum(tax_amt_post_exe/(tax_code_rate/100)), # includes TIF and district revenue!
# # TotalEAV_post_exe2 = sum((final_tax_to_dist + final_tax_to_tif)/(tax_code_rate/100)) # same as above
# ) %>%
# mutate(Exemptions_perMuni = TotalEAV_pre_exe_perMuni - TotalEAV_post_exe_perMuni,
# TaxableEAV_b4_exemps_perMuni = TaxableEAV_perMuni + Exemptions_perMuni,
# TotalRevenueCollected_perMuni = Lostrev_fromExemps_perMuni+Levy_plus_TIFrev_perMuni,
# taxrate_current = MuniLevy/TaxableEAV_perMuni,
# taxrate_noexemps = MuniLevy/TaxableEAV_b4_exemps_perMuni,
# inbetweenstep_rev_perMuni = taxrate_noexemps*TaxableEAV_perMuni,
# taxrate_change = taxrate_current - taxrate_noexemps)
#
# MuniLevy
## checking work:
# MuniLevy %>% mutate(eavcheck = TIF_increment+TaxableEAV+Exemptions,
# eavcheck2 = TotalEAV_pre_exe,
# eavcheck3 = TaxableEAV_b4_exemps+TIF_increment,
#
# revcheck = Lostrev_fromExemps+Levy_plus_TIFrev,
# revcheck2 = TotalRevenueCollected,
#
# rate_check1 = MuniLevy/TaxableEAV,
# rate_check2 = MuniLevy/TaxableEAV_b4_exemps,
#
# incrementcheck = TotalEAV_pre_exe - TaxableEAV_b4_exemps
# )
MuniLevy %>% select(agency_name,MuniLevy, everything() )
Comparison of ptaxsim/Alea calculations and the numbers for the Levy value in Josh Drucker’s Excel files. Pretty close but not perfect matches (which is fine, I’m not surprised by that).
Chicago: 6,536,952,735 vs. 6,490,629,279 JD Bridgeview: 65,534,363 vs. 65,133,190 JD Dolton: 54,957,926 vs. 56,755,459 JD Hoffman Estates: 177,316,159 vs. 182,363,626 JD Midlothian: 30,988,554 vs 30,877,423 JD Oak Park: 234,665,831 vs 233,132,867 JD.
burden_shift <- taxcode_data %>%
group_by(agency_name, major_class_code, MuniLevy, major_class_type,taxrate_current, TaxableEAV_perMuni, taxrate_noexemps, Exempted_EAV_perMuni, agency_num) %>%
summarize(
# TaxableEAV = sum((final_tax_to_dist + tax_amt_exe)/taxrate_current),
# taxrate_current = mean(taxrate_current),
# taxrate_noexemps = mean(taxrate_noexemps),
# percent of levy paid by each property class based on current exemptions
tax_burden_current = round(sum(TaxableEAV * taxrate_current/ MuniLevy), digits = 9),
# for indirect effect
tax_burden_inbetween = round(sum(TaxableEAV_b4_exemps* taxrate_current / MuniLevy), digits = 9),
# percent of levy paid by each property class if there were no exemptions
tax_burden_noexemps = round(sum(TaxableEAV_b4_exemps*taxrate_noexemps / MuniLevy), digits = 9)) %>%
mutate(
# difference in percent of the levy being paid by each property class
overall_burden_shift = tax_burden_noexemps - tax_burden_current,
direct_effect = tax_burden_inbetween - tax_burden_current,
indirect_effect = tax_burden_noexemps - tax_burden_inbetween )
burden_shift
write.csv(burden_shift, "burden_shift_allMunis_June29.csv")
burden_shift %>% filter(agency_num %in% c("030120000", "030210000", "030800000", "030920000","030580000","030310000") & major_class_code == 2)
Current burden is the % of the levy paid by each property class.
Assumption: Levy does not change - so the money the municipality needs divided by the pre_exe_eav would be the new tax rate if exemptions didn’t exist.
Note: Burden shift is a percentage point change!
Comparing the original 6 municipalities in the JD excel files:
Interpretation:
3% of the total levy for Chicago that would be paid by class 2 properties is being shifted to other property types due to current exemptions.
8.5% of the total levy in Dolton is currently shifted from class 2 residential properties to other types of properties due to current exemptions in place.
Not checked for accuarcy yet. Not complete in general. Don’t look here. - AWM 6.27.2023
Main difference for this chunk is that all exemptions except homeowners exemptions are subtracted from the EAV value to calculate the post exemption value.
# eval = FALSE, doesn't run
burdenshift<- taxcode_data %>%
# mutate(taxrate_nohome_exemps = ....)
group_by(agency_name, major_class_code, MuniLevy, major_class_type, taxrate_current, taxrate_nohome_exemps, agency_num) %>%
summarize(
# TaxableEAV = sum((final_tax_to_dist + tax_amt_exe)/taxrate_current),
# taxrate_current = mean(taxrate_current),
taxrate_no_home_exemps = mean(taxrate_noexemps),
# percent of levy paid by each property class based on current exemptions
tax_burden_current = round(sum(TaxableEAV * taxrate_current/ MuniLevy), digits = 9),
# for indirect effect
tax_burden_inbetween = round(sum(TaxableEAV_b4_exemps* taxrate_current / MuniLevy), digits = 9),
# percent of levy paid by each property class if there were no exemptions
tax_burden_no_homeexemps = round(sum((TaxableEAV_b4_exemps-exe_homeowner)*taxrate_noexemps / MuniLevy), digits = 9)) %>%
mutate(
# difference in percent of the levy being paid by each property class
overall_burden_shift = tax_burden_no_homeexemps - tax_burden_current,
direct_effect = tax_burden_inbetween - tax_burden_current,
indirect_effect = tax_burden_no_homeexemps - tax_burden_inbetween )
burden_shift %>%
filter(agency_num %in% c("030120000", "030210000", "030800000", "030920000","030580000","030310000") & major_class_code == 2)
If there were no homeowner exemptions, Calumet Park, Broadview, Maywood, Thornton, etc. would experience the largest increase in revenue collected from the elimination of homeowners exemptions. This also means that homeowners in these municipalities are being subsidized the most (as a percentage of the levy) by other types of properties.