knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)
#renv::install("ccao-data/ptaxsim")
library(tidyverse)
library(data.table)
library(gstat)
library(ptaxsim)
library(glue)
ptaxsim_db_conn <- DBI::dbConnect(RSQLite::SQLite(), "./ptaxsim.db/ptaxsim-2021.0.4.db")
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_tax_codes <- DBI::dbGetQuery(
ptaxsim_db_conn,
glue_sql("
SELECT DISTINCT agency_num, tax_code_num
FROM tax_code
WHERE agency_num IN ({muni_agency_names$agency_num*})
AND year = 2021
",
.con = ptaxsim_db_conn
)
) %>%
mutate(tax_code_num = as.numeric(tax_code_num))
tax_codes <- DBI::dbGetQuery(
ptaxsim_db_conn,
glue_sql("
SELECT DISTINCT tax_code_num, tax_code_rate
FROM tax_code
WHERE year = 2021
",
.con = ptaxsim_db_conn
)
) %>% mutate(tax_code_num = as.numeric(tax_code_num))
cross_county_lines <- c("030440000", "030585000", "030890000", "030320000", "031280000","030080000", "030560000", "031120000", "030280000", "030340000","030150000","030050000", "030180000","030500000","031210000")
muni_TC_fullyCook <- muni_tax_codes %>%
filter(!agency_num %in% cross_county_lines)
If munis that cross county lines are included, singfam_pins has 855,051 pins. When excluded, it decreases to 816,085 pins.
class_dict <- read_csv("./Necessary_Files/class_dict_singlefamcodes.csv") %>%
mutate(class_code = as.character(class_code)) # change variable type to character so the join works.
nicknames <- readxl::read_xlsx("./Necessary_Files/muni_shortnames.xlsx")
pin_data2 <- read_csv("./Output/4C_joined_PINs_bills_and_exemptions.csv")
muni_taxrates <- read_csv("./Output/4C_muni_taxrates.csv")
pin_data2 <- pin_data2 %>% left_join(class_dict)
# all pins in munis fully within cook county that are some form of single-family, detached home
singfam_pins <- pin_data2 %>%
filter(tax_code %in% muni_TC_fullyCook$tax_code_num) %>% # excludes county line crossing munis
filter(Option2 == "Single-Family")
Cook County Stats:
# Quartiles
#quantile(singfam_pins$av)
# Deciles
# quantile(singfam_pins$av, probs = c(0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1))
q = c(.25, .5, .75)
cook_quartiles <- singfam_pins %>%
filter(Option2 == "Single-Family") %>%
filter(tax_code %in% muni_TC_fullyCook$tax_code_num) %>% # excludes county line crossing munis
arrange(av) %>%
summarize(count_pins = n(),
min = min(av),
quant25 = round(quantile(av, probs = q[1])),
quant50 = round(quantile(av, probs = q[2])),
quant75 = round(quantile(av, probs = q[3])),
max = max(av))
chosen3_ranked <- singfam_pins %>%
left_join(nicknames) %>%
filter(clean_name %in% c("Dolton", "Chicago", "Glencoe")) %>%
mutate(rank = case_when(
av > (cook_quartiles$quant25-500) & (av<cook_quartiles$quant25+500) ~ "q25",
av > (cook_quartiles$quant50-500) & (av<cook_quartiles$quant50+500) ~ "q50",
av > (cook_quartiles$quant75-500) & (av<cook_quartiles$quant75+500) ~ "q75")) %>%
select(clean_name, rank, av, pin, class, everything()) %>%
filter(!is.na(rank)) # 25,413 pins kept for these 4 places
chosen3_billchange <- chosen3_ranked %>%
group_by(clean_name, rank)%>%
arrange(av) %>%
# group_by(agency_name, has_HO_exemp) %>%
mutate(#taxable_eav = final_tax_to_dist / tax_code_rate,
# current bill = current tax rate * portion of levy billed
bill_current = (final_tax_to_dist + final_tax_to_tif),
bill_noexemps = new_comp_TC_rate/100*(equalized_AV-all_exemptions+exe_homeowner),
bill_change = bill_noexemps - bill_current) %>%
group_by(clean_name, rank, has_HO_exemp) %>%
summarize(median_AV = round(median(av)),
median_EAV = round(median(eav)),
# median_bill_cur = median(bill_current),
# median_bill_new = median(bill_noexemps),
# median_change = median(bill_change),
mean_bill_cur = round(mean(bill_current, na.rm=TRUE)),
mean_bill_new = round(mean(bill_noexemps, na.rm=TRUE)),
mean_change = round(mean(bill_change, na.rm=TRUE)),
perceived_savings = median(tax_amt_exe),
cur_comp_TC_rate = round(mean(cur_comp_TC_rate), digits = 2),
new_comp_TC_rate = round(mean(new_comp_TC_rate), digits = 2),
pincount=n()
) %>%
arrange(has_HO_exemp, rank)
write_csv(chosen3_billchange, "4d_quartiles_billchange.csv")
Each Municipality has different composite tax rates. This causes the average tax bill to vary geographically within Cook County. For properties that are at the Cook County 25th percentile in assessed value ($14,000), current tax bills range from $2,800 in Chicago to over $10,000 in Dolton. The median Cook County property currently (AV= $21,000) has a $4,200 bill in Chicago, a $14,400 bill in Dolton, and a $6,000 bill in Glencoe.
If the general homestead exemption was terminated, additional EAV would become taxable which would then result in lower tax rates for all properties. This lower tax rate would result in lower tax bills for properties that were not claiming the general homestead exemption.1
When looking at those who did not claim the General Homestead Exemption (GHE) and own properties assessed at Cook County’s median AV (AV = $21,000), all tax bills would decrease from current amounts. A homeowner would experience a $500 decrease in their taxbill in Chicago and over a $2,100 decrease in Dolton if additional EAV became taxable from the termination of the GHE.
chosen3_billchange %>%
filter(has_HO_exemp == 0 & !is.na(rank)) %>%
select(Municipality = clean_name, "Percentile" = rank, "Avg Current Bill" = mean_bill_cur, "Avg New Bill" = mean_bill_new, "Tax Bill Change" = mean_change, "Perceived Savings" = perceived_savings, "# Pins in AV Range" = pincount, "AV" = median_AV, cur_comp_TC_rate, new_comp_TC_rate, everything())
When looking at those who did claim the General Homestead Exemption (GHE) and own properties assessed at Cook County’s median AV (AV = $21,000), all tax bills would increase from current amounts but the tax rate applied to all property owners would decrease. A homeowner would experience a $190 increase in their taxbill in Chicago and a $85 increase in Glencoe. While there are no properties valued at the county median AV in Dolton, a property valued at the county’s 25th percentile would experience a $650 increase if additional EAV became taxable from the termination of the GHE.
However, these hypothetical bill increases would be significantly less than property owners would initially think. All tax bills showthe amount that thetax bill was reduced due to exemptions: exempt EAV * current composite tax rate. However, this value is an over estimate of how the tax bill would change because it does not take into account the decrease in tax rate that would occur: if you increase the taxable EAV and hold the levy constant, then the tax rate decreases for all properties.
Overall, home owners believe they are benefiting more from the general homestead exemption than they would be in this scenario. Policymakers also likely believe exemptions are providing more benefits than they actually are to homeowners. While those who claim the exemption have less EAV that is taxed, the composite tax rate increases for all property owners. Ultimately some of the property tax burden is shifted from homeowners to other property owners. The amount of the burden shift depends on an area’s proportional land use (residential EAV / total EAV).2
chosen3_summarytable <- chosen3_billchange %>%
filter(has_HO_exemp == 1
#& !is.na(rank)
) %>%
#arrange(clean_name, rank) %>%
select(Municipality = clean_name, "Percentile" = rank, "Avg Current Bill" = mean_bill_cur, "Avg New Bill" = mean_bill_new, "Tax Bill Change" = mean_change, "Perceived Savings" = perceived_savings, "# Pins in AV Range" = pincount, "AV" = median_AV, cur_comp_TC_rate, new_comp_TC_rate, everything())
chosen3_summarytable
Assessed Value is used to identify the quartile breaks within each municipality for single-family properties. These break points will then be used to look at how the tax bill changes for properties with AVs at the 25th percentile, 50th percentile, and 75th percentile.
The assessed value and original equalized assessed values come from
the pin data table within PTAXSIM. This table also has
every type of exemption that the property received and the amount of EAV
that was exempt due to the exemption.
q = c(.25, .5, .75)
muni_quartiles <- pin_data2 %>%
filter(Option2 == "Single-Family")%>%
group_by(agency_name ) %>%
arrange(av) %>%
summarize(count_pins = n(),
min = min(av),
quant25 = round(quantile(av, probs = q[1])),
quant50 = round(quantile(av, probs = q[2])),
quant75 = round(quantile(av, probs = q[3])),
max = max(av)) %>%
arrange( desc( quant50))
chosen3 <- muni_quartiles %>%
left_join(nicknames) %>%
filter(clean_name %in% c("Dolton", "Chicago", "Glencoe"))
# chosen3
chosen3_ranked <- pin_data2 %>%
inner_join(chosen3, by = "agency_name") %>%
mutate(rank = case_when(
av > (quant25-500) & (av<quant25+500) ~ "q25",
av > (quant50-500) & (av<quant50+500) ~ "q50",
av > (quant75-500) & (av<quant75+500) ~ "q75")) %>%
select(clean_name, rank, av, pin, class, everything())
# chosen3_ranked %>%
# group_by(clean_name, rank)%>%
# # group_by(agency_name, has_HO_exemp) %>%
# mutate(#taxable_eav = final_tax_to_dist / tax_code_rate,
# # current bill = current tax rate * portion of levy billed
# bill_current = (final_tax_to_dist + final_tax_to_tif),
# bill_noexemps = new_comp_TC_rate/100*(equalized_AV-all_exemptions+exe_homeowner),
# bill_change = bill_noexemps - bill_current) %>%
# group_by(clean_name, rank, zero_bill, has_HO_exemp) %>%
# summarize(median_AV = median(av),
# median_EAV = median(eav),
# mean_bill_cur = mean(bill_current, na.rm=TRUE),
# median_bill_cur = median(bill_current),
# mean_bill_new = mean(bill_noexemps, na.rm=TRUE),
# median_bill_new = median(bill_noexemps),
# mean_change = mean(bill_change, na.rm=TRUE),
# median_change = median(bill_change),
# perceived_savings = median(tax_amt_exe),
# cur_comp_TC_rate = mean(cur_comp_TC_rate),
# new_comp_TC_rate = mean(new_comp_TC_rate),
# pincount=n()
# )
# chosen3_ranked %>%
# group_by(clean_name, rank)%>%
# arrange(av) %>%
# # group_by(agency_name, has_HO_exemp) %>%
# mutate(#taxable_eav = final_tax_to_dist / tax_code_rate,
# # current bill = current tax rate * portion of levy billed
# bill_current = (final_tax_to_dist + final_tax_to_tif),
# bill_noexemps = new_comp_TC_rate/100*(equalized_AV-all_exemptions+exe_homeowner),
# bill_change = bill_noexemps - bill_current) %>%
# group_by(clean_name, rank) %>%
# summarize(median_AV = round(median(av)),
# median_EAV = round(median(eav)),
# mean_bill_cur = mean(bill_current, na.rm=TRUE),
# median_bill_cur = median(bill_current),
# mean_bill_new = mean(bill_noexemps, na.rm=TRUE),
# median_bill_new = median(bill_noexemps),
# mean_change = mean(bill_change, na.rm=TRUE),
# median_change = median(bill_change),
# perceived_savings = median(tax_amt_exe),
# cur_comp_TC_rate = mean(cur_comp_TC_rate),
# new_comp_TC_rate = mean(new_comp_TC_rate),
# pincount=n()
# )
chosen3_billchange <- chosen3_ranked %>%
group_by(clean_name, rank)%>%
arrange(av) %>%
# group_by(agency_name, has_HO_exemp) %>%
mutate(#taxable_eav = final_tax_to_dist / tax_code_rate,
# current bill = current tax rate * portion of levy billed
bill_current = (final_tax_to_dist + final_tax_to_tif),
bill_noexemps = new_comp_TC_rate/100*(equalized_AV-all_exemptions+exe_homeowner),
bill_change = bill_noexemps - bill_current) %>%
group_by(clean_name, rank, has_HO_exemp) %>%
summarize(median_AV = round(median(av)),
median_EAV = round(median(eav)),
# median_bill_cur = median(bill_current),
# median_bill_new = median(bill_noexemps),
# median_change = median(bill_change),
mean_bill_cur = round(mean(bill_current, na.rm=TRUE)),
mean_bill_new = round(mean(bill_noexemps, na.rm=TRUE)),
mean_change = round(mean(bill_change, na.rm=TRUE)),
perceived_savings = median(tax_amt_exe),
cur_comp_TC_rate = round(mean(cur_comp_TC_rate), digits = 2),
new_comp_TC_rate = round(mean(new_comp_TC_rate), digits = 2),
pincount=n()
) %>%
arrange(has_HO_exemp, rank)
#chosen3_billchange
pin_data2 %>%
left_join(nicknames) %>%
filter(Option2 == "Single-Family") %>%
filter(av < 500000) %>% # remove the couple really extreme outliers in Chicago
filter(clean_name %in% c("Dolton", "Chicago", "Glencoe", "Oak Park")) %>%
ggplot( aes(y=av, group = clean_name, fill = clean_name)) +
geom_boxplot()+
# facet_wrap(~agency_name, nrow=4) +
coord_flip() +
theme_bw() +
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank()) +
labs(title = "Distribution of Residential Properties AV", y = "Assessed Value ($)") +
scale_y_continuous(label = scales::dollar)
#install.packages("ggridges")
library(ggridges)
pin_data2 %>%
left_join(nicknames) %>%
filter(Option2 == "Single-Family") %>%
filter(av < 300000) %>% # remove the couple really extreme outliers in Chicago
filter(clean_name %in% c("Dolton", "Chicago", "Glencoe")) %>%
ggplot( aes(x = av, y= clean_name, fill = clean_name ) )+
geom_density_ridges( aes(x = av, y=clean_name, fill = clean_name) )+
viridis::scale_fill_viridis(discrete = T, name = "")+
#facet_wrap(~agency_name, ncol=4) +
# coord_flip() +
theme_ridges() +
theme(#axis.text.x = element_blank(),
# axis.ticks.x = element_blank(,
legend.position = "none") +
labs(title = "Distribution of Residential Properties' AV",
y = "Assessed Value", x= "" ) +
scale_x_continuous(label = scales::dollar)
pin_data2 %>%
left_join(nicknames) %>%
filter(Option2 == "Single-Family") %>%
filter(av < 300000) %>% # remove the couple really extreme outliers in Chicago
filter(clean_name %in% c("Dolton", "Chicago", "Glencoe")) %>%
ggplot( aes(x = av, fill = clean_name ) )+
geom_histogram( )+
facet_wrap(~agency_name, nrow=4) +
theme_classic()+
theme(#axis.text.x = element_blank(),
# axis.ticks.x = element_blank(,
legend.position = "none") +
labs(title = "Distribution of Residential Properties' AV",
x = "Assessed Value", x= "# PINs" ) +
scale_x_continuous(label = scales::dollar)
pin_data2 %>%
left_join(nicknames) %>%
#filter(Alea_cats == "Owner Occupied") %>%
mutate(has_HO_exemp = factor(has_HO_exemp, levels = c("Did Not Claim", "Did Claim")),
clean_name = factor(clean_name)) %>%
filter(Option2 == "Single-Family") %>%
filter(clean_name %in% c("Dolton", "Chicago", "Glencoe", "Oak Park")) %>%
ggplot(aes(x = clean_name, y=log(av), fill = clean_name) )+
geom_violin(position = "dodge", alpha = 0.5, outlier.color = "transparent")+
viridis::scale_fill_viridis(discrete=T, name="") +
theme_classic() +
coord_flip()+
#facet_wrap(~agency_name, ncol=4) +
theme(legend.position = "none") +
labs(title = "Distribution of Residential Properties' AV", y = "log(Assessed Value)", x= "" ) #+
# scale_y_continuous(label = scales::dollar) #+ scale_x_continuous(labels = element_blank())
chosen3_billchange %>%
pivot_wider(id_cols = c(clean_name, has_HO_exemp),
names_from = "rank", values_from = "mean_change") %>%
mutate(Claimed_Exemption= ifelse(has_HO_exemp == 0 , "Didn't Claim Exemption", "Did Claim Exemption")) %>%
select(Municipality=clean_name, "Claimed_Exemption", "q25", "q50", "q75")
# chosen3_billchange %>%
# pivot_wider(id_cols = c(clean_name, has_HO_exemp, rank),
# names_from = "rank", values_from = "mean_change") %>%
# mutate(Claimed_Exemption= ifelse(has_HO_exemp == 0 , "Didn't Claim Exemption", "Did Claim Exemption")) %>%
# select(Municipality=clean_name, "Claimed_Exemption", "q25", "q50", "q75")
chosen3_billchange %>%
filter(has_HO_exemp == 0 & !is.na(rank)) %>%
select(Municipality = clean_name, "AV Percentile" = rank, "AV" = median_AV, "Average Current Bill" = mean_bill_cur, "Average New Bill" = mean_bill_new, "Tax Bill Change" = mean_change, "Perceived Savings" = perceived_savings, "# Pins in AV Range" = pincount, cur_comp_TC_rate, new_comp_TC_rate, everything())
chosen3_summarytable <- chosen3_billchange %>%
filter(has_HO_exemp == 1 & !is.na(rank)) %>%
arrange(clean_name, rank) %>%
select(Municipality = clean_name, "AV Percentile" = rank, "AV" = median_AV, "Average Current Bill" = mean_bill_cur, "Average New Bill" = mean_bill_new, "Tax Bill Change" = mean_change, "Perceived Savings" = perceived_savings, "# Pins in AV Range" = pincount, cur_comp_TC_rate, new_comp_TC_rate, everything())
chosen3_summarytable
For those that DID claim the exemption, create ratio of:
25th percentile home bill change / AV : 75th percentile home bill change/AV.
Graph the ratio points for all munis.
q = c(.25, .5, .75)
muni_quartiles <- pin_data2 %>%
filter(Option2 == "Single-Family")%>%
filter(tax_code %in% muni_TC_fullyCook$tax_code_num) %>%
group_by(agency_name ) %>%
arrange(av) %>%
summarize(count_pins = n(),
min = min(av),
quant25 = round(quantile(av, probs = q[1])),
quant50 = round(quantile(av, probs = q[2])),
quant75 = round(quantile(av, probs = q[3])),
max = max(av)) %>%
arrange( desc( quant50)) %>%
left_join(nicknames)
munis_ranked <- pin_data2 %>%
filter(tax_code %in% muni_TC_fullyCook$tax_code_num) %>%
inner_join(muni_quartiles, by = "agency_name") %>%
mutate(rank = case_when(
av > (quant25-500) & (av<quant25+500) ~ "q25",
av > (quant50-500) & (av<quant50+500) ~ "q50",
av > (quant75-500) & (av<quant75+500) ~ "q75")) %>%
select(clean_name, rank, av, pin, class, everything())
munis_billchange <- munis_ranked %>%
group_by(clean_name, rank) %>%
arrange(av) %>%
# group_by(agency_name, has_HO_exemp) %>%
mutate(#taxable_eav = final_tax_to_dist / tax_code_rate,
# current bill = current tax rate * portion of levy billed
bill_current = (final_tax_to_dist + final_tax_to_tif),
bill_noexemps = new_comp_TC_rate/100*(equalized_AV-all_exemptions+exe_homeowner),
bill_change = bill_noexemps - bill_current) %>%
group_by(clean_name, rank, has_HO_exemp) %>%
summarize(median_AV = round(median(av)),
median_EAV = round(median(eav)),
# median_bill_cur = median(bill_current),
# median_bill_new = median(bill_noexemps),
# median_change = median(bill_change),
mean_bill_cur = round(mean(bill_current, na.rm=TRUE)),
mean_bill_new = round(mean(bill_noexemps, na.rm=TRUE)),
mean_change = round(mean(bill_change, na.rm=TRUE)),
perceived_savings = median(tax_amt_exe),
cur_comp_TC_rate = round(mean(cur_comp_TC_rate), digits = 2),
new_comp_TC_rate = round(mean(new_comp_TC_rate), digits = 2),
pincount=n()
) %>%
arrange(has_HO_exemp, rank)
munis_billchange <- munis_billchange %>% left_join(muni_quartiles)
ratios<- munis_billchange %>%
filter(has_HO_exemp == 1 & !is.na(rank)) %>% # claimed exemption in 2021
mutate(billchange_to_AV_25 = ifelse(rank == "q25", mean_change/median_AV, NA)) %>%
mutate(billchange_to_AV_75 = ifelse(rank == "q75", mean_change/median_AV, NA)) %>%
group_by(clean_name) %>%
summarize(billchange_to_AV_25 = max(billchange_to_AV_25, na.rm=TRUE),
billchange_to_AV_75 = max(billchange_to_AV_75, na.rm=TRUE)) %>%
mutate(muni_ratio_25to75 = billchange_to_AV_25/billchange_to_AV_75)
ratios
billchange_to_AV_25 is the amount the tax bill changed
for properties at the municipality’s 25th percentile (based on Assessed
Value) divided by the Assessed Value of that property. The larger the
decimal, the more impact a bill reduction would have (change in property
tax bill as a proportion of the proxy for an owner’s “wealth”).
billchange_to_AV_75 is the amount that a property tax
bill changed from the removal of the general homeowner exemption / the
AV of that property.
Municipalities with high ratios will have high composite tax rates. However the further the dot is from the m=1 sloped line, the more impactful exemptions are.
ggplot(data = ratios, aes(y = billchange_to_AV_25, x = billchange_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .03, nudge_y=0.01, size = 3, check_overlap = TRUE)+
theme_classic() +
theme(legend.position = "none")
ratios %>%
filter(billchange_to_AV_25<.3) %>%
ggplot(aes(y = billchange_to_AV_25, x = billchange_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .03, nudge_y=0.01, size = 3, check_overlap = TRUE)+
theme_classic() +
theme(legend.position = "none")
ratios %>%
filter(billchange_to_AV_25<.1) %>%
ggplot(aes(y = billchange_to_AV_25, x = billchange_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .03, nudge_y=0.01, size = 3, check_overlap = TRUE)+
theme_classic() +
theme(legend.position = "none")
ratios %>%
filter(billchange_to_AV_25<.1) %>%
ggplot(aes(y = billchange_to_AV_25, x = billchange_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .03, nudge_y=0.01, size = 3, check_overlap = TRUE)+
theme_classic() +
theme(legend.position = "none")
The property tax bill divided by the assessed value is just the tax composite tax rate for the municipality.
ratios<- munis_billchange %>%
filter(has_HO_exemp == 1 & !is.na(rank)) %>% # claimed exemption in 2021
mutate(currbill_to_AV_25 = ifelse(rank == "q25", mean_bill_cur/median_AV, NA)) %>%
mutate(currbill_to_AV_75 = ifelse(rank == "q75", mean_bill_cur/median_AV, NA)) %>%
group_by(clean_name) %>%
summarize(currbill_to_AV_25 = max(currbill_to_AV_25, na.rm=TRUE),
currbill_to_AV_75 = max(currbill_to_AV_75, na.rm=TRUE)) %>%
mutate(muni_ratio_25to75 = currbill_to_AV_25/currbill_to_AV_75)
ggplot(data = ratios, aes(y = currbill_to_AV_25, x = currbill_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .03, nudge_y=0.01, size = 3, check_overlap = TRUE)+
theme_classic() +
scale_y_continuous(limits = c(0, .6))+
scale_x_continuous(limits = c(0, .6))+
theme(legend.position = "none")
ratios %>% filter(currbill_to_AV_25<.3) %>%
ggplot(aes(y = currbill_to_AV_25, x = currbill_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .02, nudge_y=0.0, size = 2.4, check_overlap = TRUE)+
theme_classic() +
theme(legend.position = "none")
ratios %>% filter(currbill_to_AV_25<.2) %>%
ggplot(aes(y = currbill_to_AV_25, x = currbill_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .02, nudge_y=0.00, size = 2.4, check_overlap = TRUE)+
theme_classic() +
theme(legend.position = "none")
new_ratios<- munis_billchange %>%
filter(has_HO_exemp == 1 & !is.na(rank)) %>% # claimed exemption in 2021
mutate(newbill_to_AV_25 = ifelse(rank == "q25", mean_bill_new/median_AV, NA)) %>%
mutate(newbill_to_AV_75 = ifelse(rank == "q75", mean_bill_new/median_AV, NA)) %>%
group_by(clean_name) %>%
summarize(newbill_to_AV_25 = max(newbill_to_AV_25, na.rm=TRUE),
newbill_to_AV_75 = max(newbill_to_AV_75, na.rm=TRUE)) %>%
mutate(muni_ratio_25to75 = newbill_to_AV_25/newbill_to_AV_75)
ggplot(data = new_ratios, aes(y = newbill_to_AV_25, x = newbill_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .03, nudge_y=0.01, size = 3, check_overlap = TRUE)+
theme_classic() +
scale_y_continuous(limits = c(0, .6))+
scale_x_continuous(limits = c(0, .6))+
theme(legend.position = "none")
new_ratios %>% filter(newbill_to_AV_25<.3) %>%
ggplot(aes(y = newbill_to_AV_25, x = newbill_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .02, nudge_y=0.0, size = 2.4, check_overlap = TRUE)+
theme_classic() +
theme(legend.position = "none")
new_ratios %>% filter(newbill_to_AV_25<.2) %>%
ggplot(aes(y = newbill_to_AV_25, x = newbill_to_AV_75, label = clean_name)) +
geom_abline(intercept = 0, slope = 1) +
geom_point(aes(alpha = .5)) +
geom_text(nudge_x = .02, nudge_y=0.00, size = 2.4, check_overlap = TRUE)+
theme_classic() +
theme(legend.position = "none")
A binary variable was created at the PIN level data for
if exe_homeowner was > 0. If any amount of EAV was exempt within
exe_homeowner, then the PIN was considered to have received the General
Homestead Exemption. That specific exemption allows up to $10,000 in EAV
to become tax exempt. Data was pulled from the pin table
used in CCAO’s PTAXSIM.↩︎
While the tax rate calculations do take into consideration TIF increments, we have not discussed the role that TIFs play in composite tax rates. If a TIF exists in the same tax code as a homeowner, the taxable EAV is frozen for years to come. However the levy amount needed to provide public services usually increases over time and results in higher tax rates for those in TIF areas compared to nearby non-TIF tax codes.↩︎