#1 Load packages
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(pastecs)
##
## Attaching package: 'pastecs'
##
## The following objects are masked from 'package:dplyr':
##
## first, last
##
## The following object is masked from 'package:tidyr':
##
## extract
library(MASS)
##
## Attaching package: 'MASS'
##
## The following object is masked from 'package:dplyr':
##
## select
library(lmtest)
## Loading required package: zoo
##
## Attaching package: 'zoo'
##
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(car)
## Loading required package: carData
##
## Attaching package: 'car'
##
## The following object is masked from 'package:dplyr':
##
## recode
##
## The following object is masked from 'package:purrr':
##
## some
#2 Load data and construct analysis variables
ahs.household.data <- read_csv("household.csv")
## Rows: 55669 Columns: 1180
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (635): CONTROL, JACPRIMARY, JACSECNDRY, JADEQUACY, JAIRRATE, JBATHEXCLU,...
## dbl (545): TOTROOMS, PERPOVLVL, OUTAGEFRQ, RENT, DINING, LAUNDY, RATINGHS, R...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
ahs.household.data <- ahs.household.data %>% mutate(tot.cost.burden = ((TOTHCAMT * 12) + MAINTAMT) / HINCP)
clean.research.data <- ahs.household.data %>%
filter(is.finite(tot.cost.burden),tot.cost.burden > 0,TENURE == "'1'",MARKETVAL > 0,HINCP > 0, MAINTAMT >= 0,PERPOVLVL >= 0,RATINGHS >= 1 & RATINGHS <= 10,YRBUILT > 0 )
clean.research.data <- clean.research.data %>%
mutate(log.cost = log(tot.cost.burden))
clean.research.data <- clean.research.data %>% mutate(housing.age = 2023 - YRBUILT)
clean.research.data <- clean.research.data %>%
mutate(age.group = case_when(
housing.age < 15 ~ "Under 15 years",
housing.age >= 15 & housing.age <= 30 ~ "15-30 years",
housing.age >= 31 & housing.age <= 50 ~ "31-50 years",
housing.age >= 51 ~ "51+ years"))
clean.research.data$age.group <- factor(
clean.research.data$age.group,
levels = c("Under 15 years", "15-30 years", "31-50 years", "51+ years"))
clean.research.data <- clean.research.data %>%
mutate(
income.group = case_when(
PERPOVLVL < 200 ~ "Lower income",
PERPOVLVL >= 200 & PERPOVLVL < 400 ~ "Moderate income",
PERPOVLVL >= 400 ~ "Higher income" ))
clean.research.data$income.group <- factor(
clean.research.data$income.group,
levels = c("Lower income",
"Moderate income",
"Higher income"))
#3 Descriptive statistics
# Continuous variables used in core models
vars <- clean.research.data %>%
dplyr::select(
tot.cost.burden,
housing.age,
MARKETVAL,
PERPOVLVL,
RATINGHS)
stat.desc(vars)
## tot.cost.burden housing.age MARKETVAL PERPOVLVL
## nbr.val 2.468900e+04 2.468900e+04 2.468900e+04 2.468900e+04
## nbr.null 0.000000e+00 1.900000e+01 0.000000e+00 0.000000e+00
## nbr.na 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
## min 4.049340e-03 0.000000e+00 1.000000e+03 1.000000e+00
## max 2.900400e+04 1.040000e+02 9.999998e+06 5.010000e+02
## range 2.900400e+04 1.040000e+02 9.998998e+06 5.000000e+02
## sum 7.458311e+04 1.217191e+06 1.303739e+10 9.054553e+06
## median 2.039832e-01 4.300000e+01 3.922040e+05 4.340000e+02
## mean 3.020904e+00 4.930094e+01 5.280649e+05 3.667444e+02
## SE.mean 1.223885e+00 1.720404e-01 4.004727e+03 9.788923e-01
## CI.mean.0.95 2.398888e+00 3.372095e-01 7.849505e+03 1.918688e+00
## var 3.698152e+04 7.307423e+02 3.959582e+11 2.365774e+04
## std.dev 1.923058e+02 2.703225e+01 6.292521e+05 1.538107e+02
## coef.var 6.365835e+01 5.483109e-01 1.191619e+00 4.193949e-01
## RATINGHS
## nbr.val 2.468900e+04
## nbr.null 0.000000e+00
## nbr.na 0.000000e+00
## min 1.000000e+00
## max 1.000000e+01
## range 9.000000e+00
## sum 2.160780e+05
## median 9.000000e+00
## mean 8.751995e+00
## SE.mean 8.556223e-03
## CI.mean.0.95 1.677071e-02
## var 1.807456e+00
## std.dev 1.344416e+00
## coef.var 1.536126e-01
# Group frequencies for ANOVA categories
table(clean.research.data$age.group)
##
## Under 15 years 15-30 years 31-50 years 51+ years
## 2396 3693 6672 11928
table(clean.research.data$income.group)
##
## Lower income Moderate income Higher income
## 4709 6574 13406
#4 Baseline linear model using logged cost burden
affordable.housing.model <- lm(log.cost ~ housing.age + MARKETVAL + PERPOVLVL + RATINGHS, data = clean.research.data)
summary(affordable.housing.model)
##
## Call:
## lm(formula = log.cost ~ housing.age + MARKETVAL + PERPOVLVL +
## RATINGHS, data = clean.research.data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -4.3973 -0.4714 0.0030 0.4416 9.7602
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 6.005e-02 3.818e-02 1.573 0.116
## housing.age -1.502e-03 1.910e-04 -7.863 3.91e-15 ***
## MARKETVAL 2.551e-07 8.282e-09 30.807 < 2e-16 ***
## PERPOVLVL -4.456e-03 3.391e-05 -131.410 < 2e-16 ***
## RATINGHS 2.552e-03 3.843e-03 0.664 0.507
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.8001 on 24684 degrees of freedom
## Multiple R-squared: 0.4124, Adjusted R-squared: 0.4123
## F-statistic: 4330 on 4 and 24684 DF, p-value: < 2.2e-16
#5 Regression assumption checks
# a) Linearity (plot and raintest)
raintest(affordable.housing.model)
##
## Rainbow test
##
## data: affordable.housing.model
## Rain = 0.97456, df1 = 12345, df2 = 12339, p-value = 0.9238
plot(affordable.housing.model, which = 1)

# b) Independence of errors (durbin-watson)
durbinWatsonTest(affordable.housing.model)
## lag Autocorrelation D-W Statistic p-value
## 1 0.03018269 1.939538 0
## Alternative hypothesis: rho != 0
# c) Homoscedasticity (plot, bptest)
plot(affordable.housing.model,which=3)

bptest(affordable.housing.model)
##
## studentized Breusch-Pagan test
##
## data: affordable.housing.model
## BP = 963.12, df = 4, p-value < 2.2e-16
# d) Normality of residuals (QQ plot, KS test)
plot(affordable.housing.model,which=2)

ks.test(affordable.housing.model$residuals, "pnorm", mean = mean(affordable.housing.model$residuals), sd = sd(affordable.housing.model$residuals))
##
## Asymptotic one-sample Kolmogorov-Smirnov test
##
## data: affordable.housing.model$residuals
## D = 0.04736, p-value < 2.2e-16
## alternative hypothesis: two-sided
# e) No multicolinarity (VIF, cor)
vif(affordable.housing.model)
## housing.age MARKETVAL PERPOVLVL RATINGHS
## 1.028008 1.047420 1.049293 1.029629
kitchen_sink_vars <- clean.research.data %>% dplyr::select(housing.age, MARKETVAL, PERPOVLVL, RATINGHS)
cor(kitchen_sink_vars)
## housing.age MARKETVAL PERPOVLVL RATINGHS
## housing.age 1.00000000 -0.02412773 -0.09321871 -0.14118329
## MARKETVAL -0.02412773 1.00000000 0.19605153 0.09351354
## PERPOVLVL -0.09321871 0.19605153 1.00000000 0.05683212
## RATINGHS -0.14118329 0.09351354 0.05683212 1.00000000
#6 Two-way ANOVA: age group and income group
anova.model <- aov(
tot.cost.burden ~ age.group * income.group,
data = clean.research.data
)
summary(anova.model)
## Df Sum Sq Mean Sq F value Pr(>F)
## age.group 3 77232 25744 0.697 0.554
## income.group 2 779052 389526 10.540 2.66e-05 ***
## age.group:income.group 6 189442 31574 0.854 0.528
## Residuals 24677 911953954 36956
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#7 Median burden by age and income group
interaction.plot(
clean.research.data$age.group,
clean.research.data$income.group,
clean.research.data$tot.cost.burden,
fun = median,
ylab = "Median Housing Cost Burden",
xlab = "Housing Age Group",
trace.label = "Income Group")

#8 Supplemental repair mechanism models
# Does housing age predict resident housing quality ratings?
condition.model <- lm(
RATINGHS ~ housing.age,
data = clean.research.data
)
summary(condition.model)
##
## Call:
## lm(formula = RATINGHS ~ housing.age, data = clean.research.data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -8.0631 -0.7962 0.2038 1.1335 1.6321
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 9.0981655 0.0176189 516.39 <2e-16 ***
## housing.age -0.0070216 0.0003134 -22.41 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.331 on 24687 degrees of freedom
## Multiple R-squared: 0.01993, Adjusted R-squared: 0.01989
## F-statistic: 502.1 on 1 and 24687 DF, p-value: < 2.2e-16
# Does housing age predict maintenance spending?
maintenance.model <- lm(
MAINTAMT ~ housing.age + PERPOVLVL + MARKETVAL,
data = clean.research.data
)
summary(maintenance.model)
##
## Call:
## lm(formula = MAINTAMT ~ housing.age + PERPOVLVL + MARKETVAL,
## data = clean.research.data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -9169 -1127 -716 120 98351
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.354e+02 6.905e+01 6.305 2.93e-10 ***
## housing.age 5.498e+00 7.714e-01 7.127 1.05e-12 ***
## PERPOVLVL 9.421e-01 1.382e-01 6.816 9.57e-12 ***
## MARKETVAL 7.860e-04 3.365e-05 23.358 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3262 on 24685 degrees of freedom
## Multiple R-squared: 0.02815, Adjusted R-squared: 0.02803
## F-statistic: 238.4 on 3 and 24685 DF, p-value: < 2.2e-16
#9 Gamma GLM robustness check
glm.data <- clean.research.data %>%
filter(tot.cost.burden < quantile(tot.cost.burden,.99))
burden.glm <- glm(
tot.cost.burden ~ housing.age + MARKETVAL + PERPOVLVL + RATINGHS,
data = glm.data,
family = Gamma(link=log)
)
summary(burden.glm)
##
## Call:
## glm(formula = tot.cost.burden ~ housing.age + MARKETVAL + PERPOVLVL +
## RATINGHS, family = Gamma(link = log), data = glm.data)
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.498e-01 4.955e-02 5.041 4.66e-07 ***
## housing.age -1.377e-03 2.467e-04 -5.581 2.42e-08 ***
## MARKETVAL 3.059e-07 1.084e-08 28.231 < 2e-16 ***
## PERPOVLVL -4.341e-03 4.489e-05 -96.699 < 2e-16 ***
## RATINGHS -1.970e-03 4.977e-03 -0.396 0.692
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for Gamma family taken to be 1.058201)
##
## Null deviance: 26417 on 24441 degrees of freedom
## Residual deviance: 12674 on 24437 degrees of freedom
## AIC: -19875
##
## Number of Fisher Scoring iterations: 9
#10 Final visualization: logged burden by income group
ggplot(clean.research.data,
aes(x=income.group,y=log.cost)) +
geom_boxplot() +
labs(
title="Housing Cost Burden by Income Group",
x="Income Group",
y="Logged Housing Cost Burden")

#test to include in final maybe
#cuz it shows something i saw on the anova more clearly which is the slope for housing age vs burden by income groups
age.by.income <- clean.research.data %>%
group_by(housing.age, income.group) %>%
summarize(median.burden = median(tot.cost.burden, na.rm = TRUE),.groups = "drop")
ggplot(age.by.income,
aes(housing.age, median.burden, color=income.group)) +
geom_line() +
coord_cartesian(xlim=c(0,50), ylim=c(0,2))

# Examine extremely low household incomes
clean.research.data %>%
summarise(
total.households = n(),
income.under.100 = sum(HINCP < 100),
income.under.1000 = sum(HINCP < 1000),
income.under.5000 = sum(HINCP < 5000),
income.under.10000 = sum(HINCP < 10000),
low.income.high.value = sum(HINCP < 1000 & MARKETVAL > 500000),
low.income.million.value = sum(HINCP < 1000 & MARKETVAL > 1000000)
)
## # A tibble: 1 × 7
## total.households income.under.100 income.under.1000 income.under.5000
## <int> <int> <int> <int>
## 1 24689 28 99 636
## # ℹ 3 more variables: income.under.10000 <int>, low.income.high.value <int>,
## # low.income.million.value <int>
# View the 15 households with the lowest reported income
clean.research.data %>%
arrange(HINCP) %>%
head(15) %>%
dplyr::select(HINCP, MARKETVAL, TOTHCAMT, MAINTAMT,
tot.cost.burden, housing.age, PERPOVLVL)
## # A tibble: 15 × 7
## HINCP MARKETVAL TOTHCAMT MAINTAMT tot.cost.burden housing.age PERPOVLVL
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 4 623472 181 1 543. 23 1
## 2 4 241651 1441 743 4509. 63 1
## 3 4 70192 424 0 1272 53 1
## 4 4 372592 741 206 2274. 33 1
## 5 4 2150337 9668 0 29004 73 1
## 6 8 395868 484 0 726 33 1
## 7 8 364111 2953 5114 5069. 53 1
## 8 8 163910 609 0 914. 53 1
## 9 8 935418 609 1 914. 43 1
## 10 8 1211079 1315 600 2048. 43 1
## 11 10 468339 955 978 1244. 63 1
## 12 10 99715 400 810 561 73 1
## 13 10 282194 1002 0 1202. 53 1
## 14 12 633853 1176 192 1192 43 1
## 15 20 59074 1022 2 613. 104 1