What are Loan Limits
Loan Limits are a policy design that assists in determining which single family residential mortgage loans are eligible for purchase, securitization and/or guarantee by the the U.S. Federal Government (FHA, VA, USDA) or Government Sponsored Enterprises (Fannie Mae and Freddie Mac).
if(!require(pacman)){
install.packages("pacman")
library(pacman)
}
p_load(tidyverse, lubridate, tidycensus, tigris, sf,
scales, xfun, rio, janitor, rvest)
options(tigris_use_cache = TRUE)
Terminology for Loan Limits:
1. Conventional Loan Limits (CLLs): Fannie Mae and Freddie Mac eligible balance
2. Government Loan Limits):
+ FHA - Forward Mortgages, FHA - Home Equity Conversion Mortgages (reverse)
+ VA Loan Limits
Factors for determining loan limits:
1. Number of units in the home
2. Geography (national, state, county)
3. Annual home price appreciation
Then there are two types of Loan Limits:
* National baseline: Floor - Defined by Home Price Appreciation (HPA) estimates
* High Cost Area Limits: ceiling - 150% of baseline for areas with greater than 115% of median home price relative to baseline
* Between the Floor and the Ceiling
How are they set?
The Home and Economic Recovery Act (HERA) outlines by statute how the loan limits are determined. The respective covered government agencies then annually make available the limits for the following year.
The following years limits are normally published in November.
1. FHA
2. Federal Housing Finance Agency - regulator of Fannie Mae and Freddie Mac
For this post, we’ll specifically focus on: Conventional Loan Limits. Below we set a series of fixed values used for performing the calculations.
# Data source for Conforming loan limits: 2022 and 2021
ref_cll_url <- c(
"https://www.fhfa.gov/DataTools/Downloads/Documents/Conforming-Loan-Limits/FullCountyLoanLimitList2022_HERA-BASED_FINAL_FLAT.xlsx",
"https://www.fhfa.gov/DataTools/Downloads/Documents/Conforming-Loan-Limits/FullCountyLoanLimitList2021_HERA-BASED_FINAL_FLAT.xlsx"
)
# Home Price Index data from FHFA
fhfa_hpi_url <- "https://www.fhfa.gov/HPI_master.csv"
# FHFA Home Price Index (HPI) used to set Conventional Conforming Loan Limits
idx_of_interest <- "traditional - expanded-data"
# Published Base Loan Limits 2021, 2022
cll_base_2021 <- 548250.00
cll_base_2022 <- 647200.00
# Conditions of Loan Limit Ceiling
cll_limit_condition <- .15
cll_hca <- 1.5
# cll_med_2021 <- cll_base_2021/(1-cll_limit_condition)
# cll_med_2022 <- cll_base_2022/(1-cll_limit_condition)
# Ceiling value for each year
cll_hca_ceilinng_2021 <- cll_base_2021*cll_hca
cll_hca_ceiling_2022 <- cll_base_2022*cll_hca
Get the Data
Below we read in the data sets:
1. Geographic data for mapping at the end
2. Read in the conforming loan limits for both 2021 and 2022
3. Read in the FHFA Home Price Index which is used to determine the 2022 limits
For future use, we define a function for reading in the HPI data to include the year-over-year home price growth measure. The Q3 reported value is the measure utilized for purposes of estimating previous year home-price appreciation that informs the following year levels.
d_sf_geo <- get_acs(geography = "county", variables = c(hinc = "B19013_001"),
geometry = T, survey = "acs5", year = 2019,
resolution = "20m")%>%
tigris::shift_geometry()
d_sf_state <- get_acs(geography = "state", variables = c(hinc = "B19013_001"),
geometry = T, survey = "acs5", year = 2019,
resolution = "20m")%>%
tigris::shift_geometry()
# Get 2 most recent years of limits (2021, 2022)
d_in_cll <- map_df(ref_cll_url,
~rio::import(file = .x, ), .id = "fname")%>%
setNames(., .[1,])%>%
.[-1,]%>%
clean_names(.)%>%
mutate_if(is.character, ~str_trim(., side = "both"))%>%
# mutate_at(.vars = vars(ends_with("unit_limit")), funs(as.double(.)))%>%
mutate(yr_cll = ifelse(x1==1, 2022, 2021),
fips_state_code = ifelse(is.na(fips_state_code), na, fips_state_code))%>%
filter(str_length(fips_state_code) == 2)%>%
select(-c(na,x1))%>%
rename(state_name = state, cbsa_code = cbsa_number)%>%
pivot_longer(one_unit_limit:four_unit_limit, names_to = "hunit_num",
values_to = "cll_amt_limit")%>%
relocate(yr_cll, .before = fips_state_code)%>%
mutate(cll_amt_limit = str_trim(cll_amt_limit, side = "both")%>%
str_replace_all(., "[^[0-9]]", "")%>%
as.numeric(),
hunit_num = case_when(
hunit_num == "one_unit_limit" ~ 1,
hunit_num == "two_unit_limit" ~ 2,
hunit_num == "three_unit_limit" ~ 3,
TRUE ~ 4))
# Read in FHFA HPI data
d_in_hpi <- read_csv(fhfa_hpi_url)%>%
mutate(
period_mo = case_when(
period == 2 ~ 4,
period == 3 ~ 7,
period == 4 ~ 10,
TRUE ~ period),
dt_full = ymd(paste(yr, period_mo, "01", sep = "-")),
hpi_type_new = paste(hpi_type, hpi_flavor, sep = " - "))
# Function to calculate home price appreciation (HPA) for national baseline (floor)
# f_get_cll_hpa <- function(df_in=NULL,fhfa_hpi_index = NULL,
# dt_hpi_reported = NULL){
# dt_hpi_reported <- dt_hpi_reported%>%ymd(.)
# dt_prev_hpi <- (lubridate::ymd(dt_hpi_reported) - lubridate::years(1))
#
# t_ref <- df_in%>%
# filter(place_id == "USA")%>%
# filter(hpi_type_new == fhfa_hpi_index)%>%
# filter(dt_full %in% c(dt_hpi_reported, dt_prev_hpi))%>%
# select(hpi_type_new, dt_full, index_sa)%>%
# arrange(dt_full)%>%
# mutate(sa_hpa12_2022 = (index_sa/lag(index_sa,1))-1)
#
# v_hpa_factor <- t_ref%>%
# na.omit()%>%
# select(sa_hpa12_2022)%>%
# pull(.)%>%
# as.numeric()
#
# # Verify if it is accurate
# check_calc <- cll_base_2021 * (1+v_hpa_factor)
# print(paste("expected: ", cll_base_2022))
# print(paste("calc result: ", check_calc))
# print(paste("statute indicates, round to the nearest $25th"))
# }
Home Price Index
# Ge most recent HPI reported values for annotating our plot
most_recent <- d_in_hpi%>%
filter(place_id == "USA")%>%
select(hpi_type_new, dt_full, index_nsa, index_sa)%>%
group_by(hpi_type_new)%>%
mutate(sa_hpa12 = (index_sa/lag(index_sa,4))-1,
nsa_hpa12 = (index_nsa/lag(index_nsa,4))-1)%>%
slice(which.max(dt_full))
# Get a subset of data for relevant time series
d_hpa <- d_in_hpi%>%
filter(hpi_type_new ==idx_of_interest)%>%
filter(year(dt_full) > 2000)%>%
filter(place_id == "USA")%>%
select(hpi_type_new, dt_full, index_nsa, index_sa)%>%
arrange(dt_full)%>%
mutate(sa_hpa12 = (index_sa/lag(index_sa,4))-1)
# Plot raw index values
p1_hpa <- d_hpa%>%
ggplot()+
geom_line(aes(x = dt_full, y = index_sa), size = 1.15)+
geom_point(data = filter(most_recent, hpi_type_new == idx_of_interest),
aes(x = dt_full, y = index_sa),
size = 3.25, color = "dodgerblue", alpha = .8)+
geom_text(data = filter(most_recent, hpi_type_new == idx_of_interest),
aes(x = dt_full-years(1), y = index_sa,
label = round(index_sa, 2)))+
scale_y_continuous(breaks = c(seq(0,375, 25)), limits = c(0,375) )+
labs(title = "FHFA HPI - Expanded Data, Raw Index Value",
subtitle = "U.S., Q3-2021",
y = "Index, Seasonally Adjusted (SA)",
x = NULL)+
theme_minimal()+
theme(axis.line.x = element_blank(),
panel.grid.major.y = element_blank())
# Plot year-over-year percent change
p2_hpa <- d_hpa%>%
ggplot()+
geom_line(aes(x = dt_full, y = sa_hpa12), size = 1.15)+
geom_hline(yintercept = 0)+
geom_point(data = filter(most_recent, hpi_type_new == idx_of_interest),
aes(x = dt_full, y = sa_hpa12),
size = 3.25, color = "dodgerblue", alpha = .8)+
geom_text(data = filter(most_recent, hpi_type_new == idx_of_interest),
aes(x = dt_full-years(1), y = sa_hpa12,
label = paste0(round(sa_hpa12*100, 4), "%")))+
scale_y_continuous(breaks = c(seq(-.2, .25, .05)), limits = c(-0.2,.25),
labels = function(x) paste0(round(x*100, 2), "%"))+
labs(title = "FHFA HPI - Expanded Data, Year-Over-Year % Change",
subtitle = "U.S., Q3-2021",
y = "Index, Seasonally Adjusted (SA)",
x = NULL)+
theme_minimal()+
theme(axis.line.x = element_blank(),
panel.grid.major.y = element_blank())
p1_hpa
p2_hpa
Conforming Loan Limits
# Create subsest of Conforming Loan Limits
d_cll_sfr_2022 <- d_in_cll%>%
filter(hunit_num == 1)%>%
filter(yr_cll == 2022)%>%
mutate(fips5 = paste0(fips_state_code, fips_county_code))%>%
select(yr_cll, fips5, hunit_num, ends_with("_name"), cll_amt_limit)%>%
mutate(cll_type = case_when(
cll_amt_limit == cll_base_2022 ~ "Base-Floor",
cll_amt_limit > cll_base_2022 & cll_amt_limit < cll_hca_ceiling_2022 ~ "Between",
cll_amt_limit == cll_hca_ceiling_2022 ~ "Ceiling"
))
# Create a 2021 v. 2022 table
d_cll_compare <- d_in_cll%>%
filter(hunit_num == 1)%>%
filter(yr_cll == 2021)%>%
mutate(fips5 = paste0(fips_state_code, fips_county_code))%>%
select(fips5, county_name, state_name, cll_2021 = cll_amt_limit)%>%
left_join(., d_in_cll%>%
filter(hunit_num == 1)%>%
filter(yr_cll == 2022)%>%
mutate(fips5 = paste0(fips_state_code, fips_county_code))%>%
select(fips5, cll_2022 = cll_amt_limit))%>%
mutate(raw_diff = cll_2022-cll_2021,
pct_limit_chng = ((cll_2022/cll_2021)-1))
# Create a summary of 2022 CLL "Types"
t1_cll <- d_cll_sfr_2022%>%
group_by(cll_type)%>%
summarize(cnt = n())%>%
knitr::kable()
# Map all U.S. Counties, filling based on Conforming Loan Limit Type
m1_cll <- d_sf_geo%>%
geo_join(., d_cll_sfr_2022, by_sp = "GEOID", by_df = "fips5")%>%
st_set_crs(st_crs(d_sf_geo))%>%
ggplot()+
geom_sf(aes(fill = cll_type), color = "white", lwd = .1, alpha = .8)+
geom_sf(data = d_sf_state, fill = NA, lwd = 0.7, color = "black")+
scale_fill_viridis_d("")+
labs(title = "Conforming Loan Limits (Conventional)",
subtitle = "As of 2022")+
theme_void()+
theme(legend.position = "bottom")
t1_cll
cll_type | cnt |
---|---|
Base-Floor | 3074 |
Between | 57 |
Ceiling | 102 |
m1_cll