Getting Data & Data Prep

# has EAV values, extensions by agency_num
agency_dt <- DBI::dbGetQuery(
  ptaxsim_db_conn,
  "SELECT *
  FROM agency
  WHERE year = 2021
  "
)

# 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 %>% 
  select(-minor_type)



#Makes a list of ALL taxing agencies, including TIFs, SSAs, 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
  "
  )


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
  )
)# %>% select(-agency_rate)

# 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
  )
)



## Read in summarized tax code level data for exemptions and taxbills.

#taxbills_by_Class_per_TC <- read_csv("taxbills_inMunis_perTC.csv")  %>% 
#  mutate(tax_code = as.character(tax_code),
    #      tax_code_frozen_eav = replace_na(tax_code_frozen_eav, 0), 
    #      tax_code_tif_increment = ifelse((tax_code_frozen_eav > tax_code_eav), 0, (tax_code_eav-tax_code_frozen_eav)),
 #   )
       #  tax_code_tif_increment = tax_code_eav-tax_code_frozen_eav) %>%
  #select(-c(year:tax_code_rate, tax_code_eav, tax_code_revenue, tax_code_distribution_pct))
  


# get rid of rpm variables, tax_amt_exe,pre and post exemption variables,
#exemptions_by_class_per_TC <- read_csv("all_exemptions_by_TC.csv") %>%
#    mutate(tax_code_num = as.character(tax_code_num))

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
  )
) %>% mutate(tax_code_num = as.character(tax_code_num))



## 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(agency_num)
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
))

#all_muni_pins <-read_csv("all_muni_pins.csv")

# Normal output from lookup_pin() command. Includes all types of exemptions
exe_dt <- lookup_pin(2021, muni_pins$pin) %>%
  setDT(key = c("year", "pin"))




taxbills_current <- tax_bill(2021, 
                  muni_pins$pin, 
                  pin_dt = exe_dt, # default option
                  simplify = FALSE)


head(taxbills_current)

TC_bills_current <- taxbills_current %>%
  left_join(class_dict, by = c("class" = "class_code")) %>% # add major property types to pin data
  group_by(major_class_code, major_class_type, tax_code) %>%
  summarize(
            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 all exemptions
            tax_amt_post_exe = sum(tax_amt_post_exe), # total rev after all 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
       ) %>%
  mutate(stage = "With exemptions")

TC_bills_current

write_csv(TC_bills_current, "TC_bills_current.csv")




# Changes all exemptions to 0 for all pins. 
# This table will then go INSIDE of the taxbill() comand
# for no exemptions simulation
no_exe_dt <- lookup_pin(2021, muni_pins$pin)# %>%
 # mutate(across(starts_with("exe_"), ~0)) %>%
 # setDT(key = c("year", "pin"))


no_exe_dt[, tax_code := lookup_tax_code(year, pin)]

exe_cols <- names(no_exe_dt)[startsWith(names(no_exe_dt), "exe_")]
taxcode_sum_no_exe <- no_exe_dt[,
  .(exe_total = sum(rowSums(.SD))),
  .SDcols = exe_cols,
  by = .(year, tax_code)
]

# recalculate the base
# change agency data table and their tax rate

t_agency_dt_no_exe <- lookup_agency(2021, no_exe_dt$tax_code)
t_agency_dt_no_exe[
  taxcode_sum_no_exe,
  on = .(year, tax_code),
  agency_total_eav := agency_total_eav + exe_total
]

no_exe_dt[, (exe_cols) := 0][, c("tax_code") := NULL]


taxbills_no_exemps <- tax_bill(2021, 
                  pin_vec = muni_pins$pin,
                  agency_dt = t_agency_dt_no_exe,
                  pin_dt = no_exe_dt,
                  simplify = FALSE
)[
  , stage := "No exemptions"
]

head(taxbills_no_exemps)

class_dict$class_code <- as.character(class_dict$class_code)



TC_bills_noexemps <- taxbills_no_exemps %>%
  left_join(class_dict, by = c("class" = "class_code")) %>% # add major property types to pin data
  group_by(major_class_code, major_class_type, tax_code) %>%
  summarize(
            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 all exemptions
            tax_amt_post_exe = sum(tax_amt_post_exe), # total rev after all 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
       )

TC_bills_noexemps

write_csv(TC_bills_noexemps, "TC_bills_noexemps.csv")

rm(taxbills_no_exemps)

Muni Land Use

EAV

using the pin data from ptaxsim, I calculate the eav of all properties, exempt EAV, the current taxbase, and taxbase if there were not exemptios

  • exempt EAV (summed from all exemption types)

  • current taxbase (using the tif_distrib table and percent of taxcode rev that goes to the tif). If a taxcode is a TIF taxcode, then do (EAV-exempt EAV) * (1-%rev that goes to TIF). If the tax code is not a TIF tax code, then use the EAV-exempt EAV.

  • taxbase without exemptions: If a taxcode is a TIF taxcode, then do EAV * (1-%rev that goes to TIF). If the tax code is not a TIF tax code, then use the eav

class_dict <- read_csv("class_dict.csv")

# use exemptions in tax codes to summarize EAV. 
# More accurate that calculating it from revenue collected.tax rate in tax bill data
exemptions_by_class_per_TC <- read_csv("all_exemptions_by_TC.csv") %>% 
  mutate(tax_code_num = as.character(tax_code_num)) %>%
  left_join(muni_tax_codes) %>%
  full_join(muni_agency_names) %>%
  left_join(nicknames) %>%
  # merge with TIF distrib table to calculate the percent of the EAV that goes to the TIF vs the district

  left_join(tif_distrib, by=c("tax_code_num", "year", "tax_code_rate")) %>%
  mutate(exempt_EAV = (exe_homeowner + exe_senior + exe_freeze + exe_longtime_homeowner + 
         exe_disabled + exe_vet_returning + exe_vet_dis + exe_abate) ,
         in_TIF = ifelse(tax_code_num %in% unique_tif_taxcodes$tax_code_num, 1, 0),
         tax_base_current = ifelse(in_TIF==1, (eav-exempt_EAV)*(1-tax_code_distribution_pct/100), eav-exempt_EAV),
         tax_base_noexemps = ifelse(in_TIF==1, (eav)*(1-tax_code_distribution_pct/100), eav),
         ResidentialProps = ifelse(major_class_code %in% c("2", "3", "9"), "Residential", "Commercial"),
         
         PropType = case_when(
           major_class_code %in% c("3","9") ~ "Multi-Family",
           major_class_code == "2" ~ "Single-Family",
           TRUE~ "Commercial-Industrial"))

grouped_exemptions <- exemptions_by_class_per_TC %>% 
   # group_by(agency_name, major_class_code, major_class_type, ResidentialProps, PropType) %>%
  group_by(clean_name,ResidentialProps, agency_name ) %>%
  summarize(eav = sum(eav),
           exempt_EAV = sum(exempt_EAV, exe_abate, na.rm=TRUE),
         tax_base_current = sum(tax_base_current, na.rm=TRUE),
         tax_base_noexemps = sum(tax_base_noexemps, na.rm=TRUE)) %>% ungroup()
  
grouped_exemptions
# calculate totals with ONLY EAV outside of TIFs
muni_eav <- grouped_exemptions %>%  
  group_by(clean_name, agency_name) %>% 
  summarize(muni_EAV_includesTIF = sum(eav), # all EAV in the municipality that exists
            muni_tax_base_current=sum(tax_base_current), # taxable EAV based on current exemptions
            muni_tax_base_noexemps = sum(tax_base_noexemps)) %>%  # taxable EAV pre-exemptions
  ungroup() 

perc_residential <- full_join(grouped_exemptions, muni_eav) %>% 
  filter(ResidentialProps == "Residential") %>% 
  mutate(percent_residential = eav / muni_EAV_includesTIF)# %>% select()

perc_residential

Percent Residential is the Residential EAV outside of TIFs / Municipality EAV outside of TIFs.

# #lowest % of eav from residential property types
# perc_residential %>% select(-ResidentialProps) %>%
#   arrange(percent_residential) %>% head()
# 
# #highest % of eav from residential property types
# perc_residential %>%  select(-ResidentialProps) %>%
#   arrange(percent_residential) %>% tail()
# 

perc_residential %>% group_by(ResidentialProps)%>%
  
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = percent_residential)) + 
  geom_sf(aes(geometry = geometry), color = "black") + theme_void()+ 
  labs(title = "Residential EAV /  Total EAV") +
    theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank()) +#+
   scale_fill_stepsn(colors = c("white","darkblue"),
                    #   limits = c(0,.6),
                       n.breaks = 5, show.limits=TRUE,
                        name = "% Residential", label = scales::percent)

Homeowner’s Exemptions Exempt EAV / Residential EAV

exemptions_to_resEAV_ratios <- grouped_exemptions %>% 
  filter(ResidentialProps == "Residential") %>%
  mutate(exemptEAV_pctof_resEAV = exempt_EAV/eav,
         nontif_ratio = exempt_EAV / tax_base_noexemps) %>% 
  select(agency_name, exemptEAV_pctof_resEAV, nontif_ratio) %>% 
  arrange(nontif_ratio)

exemptions_to_resEAV_ratios %>%
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = exemptEAV_pctof_resEAV)) + 
  geom_sf(aes(geometry = geometry), color = "black") + theme_void()+ 
  labs(title = "Exemptions / Residential EAV (in and out of TIFs)") +
    theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
   scale_fill_stepsn(colors = c("white", "darkblue"), 
                    #   limits = c(0,.6),
                       n.breaks = 5, show.limits=TRUE,
                        name = "% Residential EAV \nthat is exempt", label = scales::percent)

exemptions_to_resEAV_ratios %>%
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = nontif_ratio)) + 
  geom_sf(aes(geometry = geometry), color = "black") + theme_void()+ 
  labs(title = "Non-TIF EAV only: Homestead Exemptions / Residential EAV", caption = "Village of Phoenix skews graph. Dropped in map below") +
    theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
   scale_fill_stepsn(colors = c("white", "darkblue"), 
                    #   limits = c(0,.6),
                       n.breaks = 5, show.limits=TRUE,
                        name = "% Residential EAV \nthat is exempt", label = scales::percent)

exemptions_to_resEAV_ratios %>% 
  filter(nontif_ratio<.5) %>%
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = nontif_ratio)) + 
  geom_sf(aes(geometry = geometry), color = "black") + theme_void()+ 
  labs(title = "Percent of Residential EAV that is Tax Exempt", 
  subtitle = "Non-TIF EAV only: Homestead Exemptions / Residential EAV", caption = "Drops Village of Phoenix because it skews map colors 
       (78% of their residential EAV is tax exempt)") +
    theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
   scale_fill_stepsn(colors = c("white", "darkblue"), 
                    #   limits = c(0,.6),
                       n.breaks = 5, show.limits=TRUE,
                        name = "% Residential EAV \nthat is exempt", label = scales::percent)

District Shares

Tax base in the tables above are the total EAV outside of TIF areas.

TC_bills_current <- read_csv("TC_bills_current.csv") %>% 
  mutate(tax_code = as.character(tax_code))

class_dict$class_code <- as.character(class_dict$class_code)
 

taxcodes_current <- full_join(TC_bills_current, muni_tax_codes, 
                      by = c("tax_code" = "tax_code_num")) 

Current_Taxrates <- taxcodes_current %>% 
  left_join(muni_agency_names) %>%
  left_join(nicknames) %>%
  group_by(clean_name, agency_name) %>% 
  summarize(MuniLevy = sum(final_tax_to_dist, na.rm = TRUE), # amount billed by munis with current exemptions in place
            nonTIF_EAV_post_exemps = sum(final_tax_to_dist/(tax_code_rate/100), na.rm = TRUE),
            TIF_increment_EAV = sum(final_tax_to_tif/(tax_code_rate/100), na.rm=TRUE),  
            Exempt_EAV = sum(tax_amt_exe/(tax_code_rate/100), na.rm=TRUE), 
            Total_EAV = sum((tax_amt_exe+final_tax_to_dist+final_tax_to_tif)/(tax_code_rate/100), na.rm = TRUE)) %>%

  mutate(tax_rate_current = MuniLevy/nonTIF_EAV_post_exemps,
         nonTIF_EAV_pre_exemps = nonTIF_EAV_post_exemps + Exempt_EAV,
         taxrate_new = MuniLevy/nonTIF_EAV_pre_exemps,
         taxrate_change = tax_rate_current-taxrate_new) %>% 
  select(clean_name, taxrate_change, tax_rate_current, taxrate_new, everything()) %>% 
  arrange(desc(tax_rate_current))

land_use <- taxcodes_current %>% 
  left_join(muni_agency_names) %>% 
  left_join(Current_Taxrates) %>%
  mutate(ResidentialProps = ifelse(major_class_code %in% c("2", "3", "9"), "Residential", "Commercial"),
         PropType = case_when(
           major_class_code %in% c("3","9") ~ "Multi-Family",
           major_class_code == "2" ~ "Single-Family",
           TRUE ~ "Commercial-Industrial")) %>%
  group_by(clean_name,  ResidentialProps, agency_num, tax_rate_current, taxrate_new, taxrate_change,agency_name,) %>% 
  
  # All of the values calculated below are AFTER exemptions have been removed
  summarize(taxrev_from_proptype = sum(final_tax_to_dist, na.rm = TRUE),
            nonTIF_EAV = sum(final_tax_to_dist/(tax_code_rate/100), na.rm = TRUE),
            TIF_increment_EAV = sum(final_tax_to_tif/(tax_code_rate/100), na.rm=TRUE),
            Exempt_EAV = sum(tax_amt_exe/(tax_code_rate/100), na.rm=TRUE),
            Total_EAV = sum((tax_amt_exe+final_tax_to_dist+final_tax_to_tif)/(tax_code_rate/100), na.rm = TRUE) ) %>% ungroup()

Current_Taxrates

Current composite tax rates for each municipality are above. Table also includes the new tax rate if there were no exemptions, the levy (aka amount collected by the municipality from final_tax_to_dist variable), EAV outside of TIFs, amount of exempt EAV, and additional variables.

Below: Take EAV values within each property type, joine it with muni level EAV values, and calculate …

grouped_exemptions <- exemptions_by_class_per_TC %>% 
   # group_by(agency_name, major_class_code, major_class_type, ResidentialProps, PropType) %>%
  group_by(clean_name, major_class_type, ResidentialProps, PropType, major_class_code, agency_name) %>%
  summarize(eav = sum(eav),
           exempt_EAV = sum(exempt_EAV, exe_abate, na.rm=TRUE),
         tax_base_current = sum(tax_base_current, na.rm=TRUE),
         tax_base_noexemps = sum(tax_base_noexemps, na.rm=TRUE)) %>% ungroup()
  
grouped_exemptions
# calculate totals with ONLY EAV outside of TIFs
muni_eav <- grouped_exemptions %>%  
  group_by(clean_name, agency_name) %>% 
  summarize(muni_EAV_includesTIF = sum(eav), # all EAV in the municipality that exists
            muni_tax_base_current=sum(tax_base_current), # taxable EAV based on current exemptions
            muni_tax_base_noexemps = sum(tax_base_noexemps)) %>%  # taxable EAV pre-exemptions
  ungroup() 

pct_property_types <- left_join(grouped_exemptions, muni_eav) %>% 
  #filter(ResidentialProps == "Residential") %>% 
  mutate(pct_PropType = eav / muni_EAV_includesTIF)# %>% select()

pct_property_types
burden_table <- pct_property_types %>% 
  left_join(Current_Taxrates, by = c("agency_name", "clean_name")) %>%
  mutate(rev_collected_current = tax_base_current * tax_rate_current,
         rev_collected_new = tax_base_noexemps*taxrate_new,
         burden_current = rev_collected_current/MuniLevy,
         burden_noexemps = rev_collected_new/MuniLevy, 
         burden_change = burden_noexemps- burden_current,
         burden_noexemps = ifelse(burden_noexemps >1, 1, burden_noexemps)) %>%
  mutate(burden_current = ifelse(is.na(burden_current), 0, burden_current),
         burden_noexemps = ifelse(is.na(burden_noexemps), 0, burden_noexemps)) %>%
  mutate(burden_noexemps = ifelse(burden_noexemps>1, 1, burden_noexemps),
         burden_current = ifelse(burden_current>1, 1, burden_current)) %>%
  filter(!is.na(major_class_code))


burden_table %>% 
   
  arrange(desc(burden_change)) %>% select(clean_name, burden_change, everything()) %>%
  mutate(burden_change = round(burden_change, digits = 2))
burden_shift <- burden_table # only to not change code below in graphs. 

Compare to 6 municipalities calculated by JD:

Only class 2 property types in output below. Looks good! Yay.

burden_table %>%  filter(agency_name %in% c("CITY OF CHICAGO", "VILLAGE OF BRIDGEVIEW", "VILLAGE OF DOLTON", "VILLAGE OF HOFFMAN ESTATES","VILLAGE OF MIDLOTHIAN","VILLAGE OF OAK PARK") & major_class_code == 2) %>% select(-c(ResidentialProps, major_class_type, PropType)) %>%
  select(clean_name, muni_EAV_includesTIF, MuniLevy, exempt_EAV,burden_current, burden_noexemps)

Tax Rates

TC_bills_noexemps <- read_csv("TC_bills_noexemps.csv") %>% mutate(tax_code = as.character(tax_code))
# noexemps_rev<- taxcodes_noexemps  %>%   
#   left_join(muni_agency_names) %>%
#   select(tax_code, major_class_code, major_class_type, final_tax_to_dist, agency_name) %>% 
#   rename(final_tax_to_dist_noexemps = final_tax_to_dist)

taxcodes_noexemps <- left_join(TC_bills_noexemps, muni_tax_codes, 
                      by = c("tax_code" = "tax_code_num"))  %>%
    left_join(muni_agency_names) %>%
  left_join(nicknames)


noexemps_munisummary <- taxcodes_noexemps %>% 
  group_by(clean_name, agency_name) %>% 
  summarize(MuniLevy = sum(final_tax_to_dist, na.rm = TRUE), #amount billed by muni within cook county
            nonTIF_EAV = sum(final_tax_to_dist/(tax_code_rate/100), na.rm = TRUE), # within cook county
           final_tax_to_dist = sum(final_tax_to_dist, na.rm=TRUE),
     TIF_increment_EAV = sum(final_tax_to_tif/(tax_code_rate/100), na.rm=TRUE),  # within cook county
            Exempt_EAV = sum(tax_amt_exe/(tax_code_rate/100), na.rm=TRUE), # within cook county
            Total_EAV = sum((tax_amt_exe+final_tax_to_dist+final_tax_to_tif)/(tax_code_rate/100), na.rm = TRUE)) %>%

  mutate(taxrate_noexemps = MuniLevy/nonTIF_EAV,
        # nonTIF_EAV= nonTIF_EAV + Exempt_EAV,
       ) 
 # select(agency_name, tax_rate_noexemps, everything()) %>% 
#  arrange(desc(tax_rate_noexemps))


noexemps_munisummary

6 Highest and 6 lowest tax rates. View the extremes on both sides

Current_Taxrates %>%  filter(tax_rate_current > 0.25 )
Current_Taxrates %>%  filter( tax_rate_current < 0.08)

Map of Tax Rate Change

burden_shift %>% 
    filter(major_class_code == 2) %>% # all property types have same composite tax rate
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = tax_rate_current)) + 
      theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
  # scale_fill_gradientn(colors = c("white", "maroon"), 
  scale_fill_stepsn(colors = c("white", "#76C3D6", "#1A5E7D"),
                      limits = c(0,.35),
                    show.limits=TRUE,
                       n.breaks = 5,
                        name = "Tax Rate", label = scales::percent)+
  geom_sf(aes(geometry = geometry), color = "black") +  
  labs(title = "Current composite tax rates with current exemptions in place" ,    
         caption = "Highest composite tax rate is in Park Forest (41.4%.)
       Lowest composite tax rate is in Oak Brook (6.3%) and Chicago (6.7%).")

burden_shift %>% 
    filter(major_class_code == 2) %>% # all property types have same composite tax rate
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = taxrate_new)) + 
      theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
     scale_fill_stepsn(colors = c("white", "#76C3D6", "#1A5E7D"),
                      limits = c(0,.35),
                       n.breaks = 5, show.limits=TRUE,
                        name = "Tax Rate", label = scales::percent)+
  geom_sf(aes(geometry = geometry), color = "black") +  
  labs(title = "New composite tax rates if exemptions were eliminated" ,    
         caption = "Data from CCAO & PTAXSIM. Unincorporated areas are filled with gray.")

Current_Taxrates %>% 
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = taxrate_change*100)) + 
  geom_sf(aes(geometry = geometry), color = "black") +  
      theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
  
    scale_fill_stepsn(colors = c("#ffffcc","#a1dab4" ,"#41b6c4","#2c7fb8", "#253494"),
 # low = "#ffffcc", mid = "#41b6c4", high = "#253494",
#  space = "Lab",
                       n.breaks = 6, show.limits=TRUE,
                        name = "Percentage Point Difference")+
  labs(title = "Change in Composite Tax Rate if Exemptions are Removed")

    #    scale_fill_stepsn(colors = c("white", "#76C3D6", "#1A5E7D"),
                  #    limits = c(0,.35),

Current_Taxrates %>% 
 # filter(clean_name != "Park Forest") %>%
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = taxrate_change*100)) + 
  geom_sf(aes(geometry = geometry), color = "black") +  
      theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
  
    scale_fill_stepsn(colors = c("#ffffcc","#a1dab4" ,"#41b6c4","#2c7fb8", "#253494"),
 # low = "#ffffcc", mid = "#41b6c4", high = "#253494",
#  space = "Lab",
                       n.breaks = 5, show.limits=TRUE,
                        name = "Percentage Point Difference")+
  labs(title = "Change in Composite Tax Rate if Exemptions are Removed", subtitle = "Drops Park Forest")

# as a dot graph ## 

order <- burden_shift %>%  
  as_tibble() %>% 
  summarize(agency_name = unique(agency_name), 
            clean_name = unique(clean_name), 
            tax_rate_current = unique(tax_rate_current), 
            taxrate_new = unique(taxrate_new)) %>% 
  arrange(tax_rate_current) %>%
  select(agency_name, clean_name, tax_rate_current)
median(order$tax_rate_current)
## [1] 0.1212
head(order)
tail(order)
# look at ones that changed the most
burden_shift %>% 
  filter(tax_rate_current < (median(tax_rate_current))+0.002 & tax_rate_current > (median(tax_rate_current))-0.002 |
   (tax_rate_current > 0.25 | tax_rate_current < 0.08 )) %>%
  #          (tax_rate_current < (median(tax_rate_current))+0.005 & tax_rate_current > (median(tax_rate_current))-0.005 )) %>% 
  filter(PropType == "Single-Family") %>%
  ungroup() %>% 
  select(clean_name, tax_rate_current, taxrate_new, agency_name) %>% 
  pivot_longer(c("tax_rate_current", "taxrate_new"), 
               names_to = "type", values_to = "tax_rate") %>% 
  left_join(order) %>%
  ggplot(aes(x = tax_rate*100, y= reorder(clean_name, tax_rate_current)))+
  geom_line(aes(group = clean_name))+ 
   geom_point(aes(color=type), size=3 )+
  theme_minimal() + 
  theme( 
    legend.title = element_blank(),
               plot.title.position = "plot",
     #   panel.background = element_rect(fill='transparent'), #transparent panel bg
    plot.background = element_rect(fill='transparent', color=NA) #transparent plot bg
   )+
        scale_color_brewer(palette="Paired", labels = c("Exemptions", "No Exemptions"), direction = 1)+

  labs(title = "Difference in Composite Tax Rate if there were No Exemptions",
       subtitle = "Ordered by Current Composite Tax Rate", x = "Composite Tax Rate (%)", y = "" , caption = "For the highest, median, and lowest municipality composite tax rates ")

# ordered by change in tax rate if exemptions were removed.
# as a dot graph ## 

order <- burden_shift %>% 
  as_tibble() %>%
  group_by(agency_name, clean_name) %>%
    summarize(tax_rate_current = mean(tax_rate_current), 
            taxrate_new = mean(taxrate_new),
            taxrate_change = mean(taxrate_change)) %>%
  arrange(taxrate_change)
median(order$taxrate_change) # median rate change is 1.4 percentage points
## [1] 0.01361
head(order)
# this one probably not the best Delete code soon.
# look at ones that changed the most
# burden_shift %>%  
#   filter(tax_rate_current < (median(tax_rate_current))+0.002 & tax_rate_current > (median(tax_rate_current))-0.002 |
#    (tax_rate_current > 0.25 | tax_rate_current < 0.08 )) %>% 
#   filter(PropType == "Single-Family") %>%
#   ungroup() %>% 
#   select(clean_name, tax_rate_current, taxrate_new, agency_name) %>% 
#   pivot_longer(c("tax_rate_current", "taxrate_new"), 
#                names_to = "type", values_to = "tax_rate") %>% 
#   left_join(order) %>%
#   ggplot(aes(x = tax_rate*100, y= reorder(clean_name, taxrate_change)))+
#   geom_line(aes(group = clean_name))+ 
#    geom_point(aes(color=type), size=3 )+
#   theme_minimal() + 
#   theme( 
#     legend.title = element_blank(),
#                plot.title.position = "plot",
#      #   panel.background = element_rect(fill='transparent'), #transparent panel bg
#     plot.background = element_rect(fill='transparent', color=NA) #transparent plot bg
#    )+
#         scale_color_brewer(palette="Paired", labels = c("Exemptions", "No Exemptions"), direction = 1)+
# 
#   labs(title = "Difference in Composite Tax Rate if there were No Exemptions", subtitle = "Ordered by Change in Tax Rate: Top and Bottom Observations",
#        x = "Composite Tax Rate (%)", y = "" , caption = "For the highest and lowest composite tax rates")

burden_shift %>%  
  filter(taxrate_change < 0.002 |taxrate_change > 0.055  |
           taxrate_change < (median(taxrate_change))+0.00059 & taxrate_change > (median(taxrate_change))-0.00059
         ) %>% 
  filter(PropType == "Single-Family") %>%
  ungroup() %>% 
  select(clean_name, tax_rate_current, taxrate_new, agency_name) %>% 
  pivot_longer(c("tax_rate_current", "taxrate_new"), 
               names_to = "type", values_to = "tax_rate") %>% 
  left_join(order) %>%
  ggplot(aes(x = tax_rate*100, y= reorder(clean_name, taxrate_change)))+
  geom_line(aes(group = clean_name))+ 
   geom_point(aes(color=type), size=3 )+
  theme_minimal() + 
  theme( 
    legend.title = element_blank(),
               plot.title.position = "plot",
     #   panel.background = element_rect(fill='transparent'), #transparent panel bg
    plot.background = element_rect(fill='transparent', color=NA) #transparent plot bg
   )+
        scale_color_brewer(palette="Paired", labels = c("Exemptions", "No Exemptions"), direction = 1)+

  labs(title = "Difference in Composite Tax Rate if there were No Exemptions", subtitle = "Ordered by Change in Tax Rate: Highest, Median, and Smallest Differences",
       x = "Composite Tax Rate (%)", y = "" )

Single-Family (Class 2) Burden Shift

Change in Tax Burden for Class 2 Properties:

burden_shift %>%
      mutate(burden_current = ifelse(burden_current>1, 1, burden_current)) %>%

      filter(major_class_code == 2) %>%

  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%

  ggplot(aes(fill = burden_current)) + 
  geom_sf(aes(geometry = geometry), color = "black") + 
  theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
  # scale_fill_gradientn(
   scale_fill_stepsn(colors = c("#ffffcc","#a1dab4" ,"#41b6c4","#2c7fb8", "#253494"),
                        show.limits=TRUE, n.breaks = 6,
                        name = "Burden with \nExemptions", labels = scales::percent
                    )+
  labs(title = "Current share of property tax burden", subtitle = "for Class = 2 Property Types")

burden_shift %>% 
      filter(major_class_code == 2) %>%

  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%

  ggplot(aes(fill = burden_noexemps)) + 
  geom_sf(aes(geometry = geometry), color = "black") +     
  theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
   #scale_fill_gradientn(
    # scale_fill_binned(
     #colors = c("#f0f9e8", "#0868ac"),
   #  colors = c('#a6611a','#018571'),
                       #limits = c(0,.6),
           #            n = 6,
   #   name = "Burden w/o \nExemptions", label = scales::percent)+
  
  scale_fill_stepsn(colors = c("#ffffcc","#a1dab4" ,"#41b6c4","#2c7fb8", "#253494"),
 # low = "#ffffcc", mid = "#41b6c4", high = "#253494",
#  space = "Lab",
  n.breaks = 6, na.value = "grey50",
  guide = "coloursteps",
  aesthetics = "fill", show.limit = TRUE,
  name = "Burden w/o \nExemptions",
label = scales::percent
)+
 labs(title = "New share of property tax burden", subtitle = "for Class = 2 Property Types")

burden_shift %>% filter(clean_name != "Phoenix") %>%
  filter(major_class_code == 2) %>%
  mutate(burden_change = ifelse(burden_change<0,0, burden_change))%>%
     #    burden_change = ifelse(burden_change>1,1, burden_change))%>%
  full_join(muni_shp, by = c("agency_name" = "AGENCY_DESC")) %>%
  ggplot(aes(fill = (0-burden_change*100)) )+ 
  geom_sf(aes(geometry = geometry), color = "black") + theme_void()+ 
  labs(title = "Change in Residential Share of Tax Burden") +
    theme_classic() + 
  theme(axis.ticks = element_blank(), axis.text = element_blank())+
  
    scale_fill_stepsn(colors = c("#253494", "#2c7fb8", "#41b6c4", "#a1dab4", "#ffffcc"),
 # low = "#ffffcc", mid = "#41b6c4", high = "#253494",
#  space = "Lab",
  n.breaks = 5,
  na.value = "grey50",
  guide = #"legend", 
   "coloursteps",
show.limit = TRUE,
  name = "Percentage Point \nDifference")

   # scale_fill_steps( low= "white", high = "darkblue",
   #   #colors = c("darkblue","white"), 
   #   show.limits = TRUE,
   #                     n.breaks = 5,
   #                      name = "Difference in Burden")
  
   # geom_sf(data = countyIL, fill=NA, color="dark gray")
# as a dot graph ## 

order <- burden_shift %>% 
  ungroup %>% as_tibble() %>%
  #  filter(ResidentialProps == "Residential") %>%

  filter(PropType == "Single-Family") %>%
  select(agency_name, clean_name, burden_current, burden_change)



# burder_shift_ordered <-  burden_shift %>% 
#   ungroup() %>% 
#   select(agency_name, current_burden, no_exemptions_burden) %>%    
#   pivot_longer(c("current_burden", "no_exemptions_burden"), 
#                names_to = "type", values_to = "pct_burden") %>% 
#   left_join(order)

# look at ones that changed the most

burden_shift %>% filter(PropType == "Single-Family")%>%
summarize(median_change = median(burden_change),
          median_current = median(burden_current),
          median_noexemps = median(burden_noexemps)) # 0.0442



burden_shift %>%  
  filter(PropType == "Single-Family") %>%

  filter(burden_current > 0.9 |burden_current < .3 |
          ( (burden_current < median(burden_current) + 0.01 )& (burden_current > median(burden_current) - 0.01)) )%>% 
  #filter(ResidentialProps == "Residential") %>%

  ungroup() %>% 
  select(agency_name, clean_name, burden_current, burden_noexemps,  burden_change) %>% 
  arrange(burden_change) %>%
  mutate( 
         burden_noexemps = ifelse(burden_noexemps > 1, 1, burden_noexemps)) %>%
  pivot_longer(c("burden_current", "burden_noexemps"), 
               names_to = "type", values_to = "pct_burden") %>% 
  inner_join(order) %>%
  ggplot(aes(x = pct_burden*100, 
             y= reorder(clean_name, burden_change)))+
            # y= reorder(clean_name, burden_current)))+
  geom_line(aes(group = clean_name))+ 
   geom_point(aes(color=type), size=3 )+
  theme_minimal() + 
  theme(#legend.position = "none", 
    legend.title = element_blank(),
               plot.title.position = "plot",
     #   panel.background = element_rect(fill='transparent'), #transparent panel bg
    plot.background = element_rect(fill='transparent', color=NA) #transparent plot bg
   )+
        scale_color_brewer(palette="Paired", labels = c("No Exemptions", "Exemptions"), direction = 1)+

  labs(title = "Change in Single-family Residential Tax Burden", 
       subtitle = "Ordered by Current Burden Levels",
  x = "Share of Levy (%)", y = "" , 
  caption = "Residential Tax Burden is the Share of the property tax collected that was paid for by 
     single-family home owners in property classes 2.") #+

# scale_x_continuous(label = scales::percent)
### Change in Burden
burden_shift %>%    
  ungroup() %>% 
    filter(PropType == "Single-Family") %>%
   # filter(burden_change > 0.3 | burden_change < .01) %>% 

  select(agency_name, clean_name, burden_current, burden_noexemps,
         burden_change) %>% 
  mutate(
         burden_current = ifelse(burden_current > 1, 1, burden_current)) %>%
  pivot_longer(c("burden_current", "burden_noexemps"),
               names_to = "type", values_to = "pct_burden") %>%
  inner_join(order) %>%
  ggplot(aes(x = pct_burden, y= reorder(clean_name, burden_current)))+
  geom_line(aes(group = clean_name))+ 
   geom_point(aes(color=type), size=3 )+
  theme_minimal() + 
  theme(#legend.position = "none", 
    legend.title = element_blank(),
               plot.title.position = "plot",
     #   panel.background = element_rect(fill='transparent'), #transparent panel bg
    plot.background = element_rect(fill='transparent', color=NA) #transparent plot bg
   )+
        scale_color_brewer(palette="Paired", labels = c("No Exemptions", "Exemptions"), direction = 1)+

  labs(title = "Change in Single-family Residential Tax Burden", 
  x = "Share of Levy (%)", y = "" , 
  caption = "Residential Tax Burden is the Share of the property tax collected that was paid for by 
     single-family home owners in property classes 2.")




burden_shift %>%    
  ungroup() %>% 
    filter(PropType == "Single-Family") %>%
    filter(burden_noexemps > 0.2 | burden_noexemps < .001) %>% 

  select(agency_name, clean_name, burden_current, burden_noexemps,
         burden_change) %>% 
  mutate(
         burden_current = ifelse(burden_current > 1, 1, burden_current)) %>%
  pivot_longer(c("burden_current", "burden_noexemps"),
               names_to = "type", values_to = "pct_burden") %>%
  inner_join(order) %>%
  ggplot(aes(x = pct_burden, y= reorder(clean_name, burden_change)))+
  geom_line(aes(group = clean_name))+ 
   geom_point(aes(color=type), size=3 )+
  theme_minimal() + 
  theme(#legend.position = "none", 
    legend.title = element_blank(),
               plot.title.position = "plot",
     #   panel.background = element_rect(fill='transparent'), #transparent panel bg
    plot.background = element_rect(fill='transparent', color=NA) #transparent plot bg
   )+
        scale_color_brewer(palette="Paired", labels = c("No Exemptions", "Exemptions"), direction = 1)+

  labs(title = "Change in Single-family Residential Tax Burden", 
  x = "Share of Levy (%)", y = "" , 
  caption = "Residential Tax Burden is the Share of the property tax collected that was paid for by 
     single-family home owners in property classes 2.")

Summary table of Burden Shift: 3 Property Categories

Same data that is used above but property types are combined into 3 categories:

Single-family (Class 2),
Multi-Family (Class 3 & 9),
and Commercial-Industrial (all other Classes: 1,4, 5, 6, 7, 8, 9)

# Current Burden:

munis_3property_types <- burden_shift %>%# mutate(
     #       burden_noexemps = ifelse(is.na(burden_noexemps), 0, burden_noexemps)) %>%
  #    mutate(burden_current = ifelse(burden_current>1, 1, burden_current)) %>%
  ungroup() %>% 
  group_by(agency_name, clean_name, PropType) %>%
  summarize(#final_tax_to_dist = sum(final_tax_to_dist, na.rm = TRUE),
            burden_current = sum(burden_current, na.rm = TRUE),
            burden_noexemps = sum(burden_noexemps, na.rm = TRUE),
            burden_change = sum(burden_change, na.rm = TRUE)) 

#write.csv(munis_3property_types, "1_usemetable.csv")

proptypes3_current <- munis_3property_types %>% 
    mutate(burden_current = ifelse(is.na(burden_current), 0, burden_current)) %>%

  pivot_wider( id_cols = clean_name , names_from = "PropType", values_from = "burden_current",names_prefix="Current - ", values_fill = 0 ) %>% 
  select(clean_name, `Current - Single-Family`, everything()) %>%
  arrange(-`Current - Single-Family`)

proptypes3_current[2:4] <- sapply(proptypes3_current[2:4], function(x) scales::percent(x, accuracy=.01) )
                                   
proptypes3_current
# __Tax Burden if there were no exemptions:__

proptypes3_noexemps <- munis_3property_types %>% 
  mutate(burden_noexemps = ifelse(is.na(burden_noexemps), 0, burden_noexemps)) %>%
  pivot_wider( id_cols = clean_name , names_from = "PropType", values_from = "burden_noexemps", names_prefix = "W/O Exemptions - ", values_fill = 0)  
proptypes3_noexemps[2:4] <- sapply(proptypes3_noexemps[2:4], function(x) scales::percent(x, accuracy=.01))
proptypes3_noexemps

Change in Share of Burden if there were no exemptions:

# burden_shift %>% ungroup() %>% 
#   group_by(agency_name, PropType) %>%
#   summarize(district_rev_collected = sum(district_rev_collected),
#             current_burden = sum(current_burden),
#             no_exemptions_burden = sum(no_exemptions_burden)) 

props_wide <- munis_3property_types %>%
  pivot_wider(id_cols = clean_name , 
               names_from = "PropType", 
               values_from = "burden_change", values_fill = 0) %>% select(clean_name, `Single-Family`, everything()) %>%
  arrange(desc(`Single-Family`) )

props_wide[2:4] <- sapply(props_wide[2:4], function(x) scales::percent(x, accuracy=.001))
props_wide
proptypes3_comparisontable<- left_join(proptypes3_current, proptypes3_noexemps, by = "clean_name")
proptypes3_comparisontable
proptypes3_burdenchange <- munis_3property_types %>% 
  mutate(burden_noexemps = ifelse(is.na(burden_change), 0, burden_change)) %>%
  pivot_wider( id_cols = agency_name , names_from = "PropType", values_from = "burden_change", values_fill = 0) %>%
  arrange(desc(`Single-Family`))

proptypes3_burdenchange[2:4] <- sapply(proptypes3_burdenchange[2:4], function(x) scales::percent(x, accuracy=.01))
proptypes3_burdenchange

Exporting data to Excel file

library(openxlsx)

dataset_names <- list(
  'Land Use' = perc_residential,
  'Burden Table' = burden_table,
  # 'EAVoutsideTIFs' = EAV_outside_TIFS_byClass,
  
  'Composite Tax Rates' = Current_Taxrates,
  'MuniEAV' = muni_eav, #included as columns in Burden Table too
 #  'ResidentialEAV' = table_ResidentialEAV,  # doesn't exist?
  # 'Burden with Exemps' = burden_with_exemptions,
  # 'Burden without Exemps' = burden_noexemps,
  'Burden Shift-3PropTypes' = proptypes3_burdenchange,
  '3 Property Types'= munis_3property_types,
  'TaxcodeData-NoExemptions'=taxcodes_noexemps,
  'TaxcodeData-Current' = taxcodes_current)

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