Income vs. Rent Growth, Part 2
In the previous post, I looked at median gross rent and median household income growth from 2005 - 2023 according to the American Community Survey. But that analysis was only at the national level. What about the state level? Let’s find out.
Again, I’ll pull the median household income and median gross rent from the ACS. But this time I’ll select states. Additionally, I’ll switch to looking at median household income only for renter households. Finally, rather than looking at the full time series, I’ll only pull the starting and ending years.
state_hh_income <- get_ACS("NAME,B25119_003E","state",2005,2005,1)
state_hh_income2 <- get_ACS("B25119_003E","state",2023,2023,1)
state_hh_income$inc05 <- as.numeric(state_hh_income$B25119_003E)
state_hh_income2$inc23 <- as.numeric(state_hh_income2$B25119_003E)
state_hh_income <- cbind(state = state_hh_income$NAME,
inc05 = state_hh_income$inc05,
inc23 = state_hh_income2$inc23)
state_hh_income <- as_tibble(state_hh_income)
state_hh_income <- state_hh_income %>%
mutate(inc_growth = as.numeric(inc23) / as.numeric(inc05) - 1)
state_hh_rent <- get_ACS("B25064_001E,NAME","state",2005,2005,1)
state_hh_rent2 <- get_ACS("B25064_001E","state",2023,2023,1)
state_hh_rent$rent05 <- as.numeric(state_hh_rent$B25064_001E)
state_hh_rent2$rent23 <- as.numeric(state_hh_rent2$B25064_001E)
state_hh_rent <- cbind(state = state_hh_rent$NAME,
rent05 = state_hh_rent$rent05,
rent23 = state_hh_rent2$rent23)
state_hh_rent <- as_tibble(state_hh_rent)
state_hh_rent <- state_hh_rent %>%
mutate(rent_growth = as.numeric(rent23) / as.numeric(rent05) - 1
)
state_income_rent <- merge(state_hh_income, state_hh_rent, by = "state")
state_income_rent <- state_income_rent %>%
mutate(diff = rent_growth - inc_growth)
state_income_rent <- arrange(state_income_rent, desc(diff))
state_income_rent2 <- state_income_rent %>%
select(state, inc_growth, rent_growth, diff) %>%
mutate(across(c("inc_growth", "rent_growth", "diff"), function(x) (paste0(round(x, 4) * 100,"%"))))
States with the largest difference:
knitr::kable(head(state_income_rent2))
state | inc_growth | rent_growth | diff |
---|---|---|---|
Arizona | 90.42% | 124.27% | 33.85% |
Florida | 81.34% | 112.48% | 31.14% |
Hawaii | 66.36% | 94.97% | 28.62% |
Delaware | 45.8% | 71.25% | 25.45% |
Nevada | 63.83% | 88.39% | 24.56% |
Wyoming | 61.81% | 86.22% | 24.41% |
Arizona and Florida had some of the fastest rent growth post-Covid, so this makes sense.
States with the smallest difference:
knitr::kable(tail(state_income_rent2))
state | inc_growth | rent_growth | diff | |
---|---|---|---|---|
47 | Ohio | 74.57% | 65.42% | -9.15% |
48 | Puerto Rico | 55.94% | 46.58% | -9.37% |
49 | Vermont | 86.61% | 75.99% | -10.62% |
50 | West Virginia | 87.45% | 75.98% | -11.47% |
51 | Illinois | 80.26% | 68.66% | -11.59% |
52 | District of Columbia | 144.39% | 128.85% | -15.54% |
Let’s redo the national analysis, but this time with the renter median household income.
us_hh_income <- get_ACS("B25119_003E","us",2005,2019,1)
us_hh_income2 <- get_ACS("B25119_003E","us",2021,2023,1)
us_hh_income <- rbind(us_hh_income, us_hh_income2)
us_rent <- get_ACS("B25064_001E","us",2005,2019,1)
us_rent2 <- get_ACS("B25064_001E","us",2021,2023,1)
us_rent <- rbind(us_rent, us_rent2)
us_hh_income$B25119_003E[1]
## [1] "28251"
us_hh_income <- us_hh_income %>%
mutate(income_index = as.numeric(B25119_003E) * 100 / 28251)
rent_start <- us_rent$B25064_001E[1]
us_rent <- us_rent %>%
mutate(rent_index = as.numeric(B25064_001E) * 100/ 728)
rent_income <- inner_join(us_hh_income, us_rent, by = "year")
rent_income$Difference <- rent_income$rent_index - rent_income$income_index
total_change <- rent_income %>%
select(rent_index, income_index, Difference)
total_change <- total_change[18,]
total_change2 <- total_change %>%
mutate(across(everything(), function(x) (paste0(round(x, 2),"%"))))
knitr::kable(total_change2)
rent_index | income_index | Difference |
---|---|---|
193.13% | 183.07% | 10.06% |
df <- rent_income %>%
select(year, rent_index, income_index) %>%
rename(renter_income_index = income_index) %>%
gather(key = "variable", value = "value", -year)
x <- ggplot(df, aes(x = year, y = value)) +
geom_line(aes(color = variable), size = 1.5) +
scale_color_manual(values = c("Orange", "cyan3")) +
labs(caption = "Source: American Community Survey \n @abibler.bsky.social",
title =
"Median Gross Rent vs. Median Household Income (Renters), \n 2005 = 100") +
ylab("Index") +
xlab("Year") +
theme_minimal() +
scale_y_continuous(breaks=(seq(100, 200, 25)), limits = c(100, 200)) +
theme(legend.title = element_blank(),
panel.grid.major.x = element_blank(),
panel.grid.minor.x = element_blank(),
plot.title = element_text(size = 18, face = "bold"))
x
We can see that this time the difference is “only” 10%, and now the difference seems to be more due to the post-Covid rent spike.