Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
318 lines (261 sloc) 16.4 KB
---
title: Rents and vacancy rates
author: Jens von Bergmann
date: '2019-10-16'
slug: rents-and-vacancy-rates
categories:
- cmhc
- rental
- Vancouver
tags: []
description: "Adding some context to vacancy rates by rent segment."
featured: ''
images: ["https://doodles.mountainmath.ca/posts/2019-10-16-rents-and-vacancy-rates_files/figure-html/vacancy_by_quintile-1.png"]
featuredalt: ""
featuredpath: ""
linktitle: ''
type: "post"
draft: false
blackfriday:
fractions: false
hrefTargetBlank: true
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(
echo = FALSE,
message = FALSE,
warning = FALSE,
fig.width=8,
cache = FALSE
)
library(tidyverse)
library(cancensus)
library(cmhc)
library(cansim)
```
This post responds to a misconception about rental housing that has been making the rounds. Our housing crisis is fundamentally a rental crisis, so it’s important to keep the numbers straight so that we can better focus our energy and resources.
The misconception originate from the [2019 Vancouver Housing Data Book](https://vancouver.ca/files/cov/2019-housing-vancouver-annual-progress-report-and-data-book.pdf). The data book is a huge effort to compile and has a host of valuable information. Vancouver has been doing this for the second year now, and it is successively getting better. But there are bound to be some hiccups on the road, and these are two of them.
At issue here is not the data presented, the data is correct. But the choice of data, and how it is presented, lends itself to misconceptions.
## Expensive rental units are vacant, (so we don’t need more)
```{r}
cov_region_params=cmhc_region_params(geography = "Vancouver",type="CSD")
params <- cmhc_timeseries_params(table_id = "2.2.26",region=cov_region_params)
universe <- get_cmhc(params) %>%
mutate(Date=as.Date(paste0(X1," 01"),format="%Y %B %d")) %>%
select(-X1,-X2) %>%
mutate(Year=strftime(Date,"%Y"))
```
This stat comes from [page 183 of the Vancouver Housing Data Book](https://vancouver.ca/files/cov/2019-housing-vancouver-annual-progress-report-and-data-book.pdf#page=183). It shows that the vacancy rate for apartments renting over $3,750 is 8.7%. That's out of the range of *reasonable vacancy* and at the bottom end of what the [Lincoln Institute of Land Policy considers a *Moderately High Vacancy*](https://www.lincolninst.edu/sites/default/files/pubfiles/empty-house-next-door-full.pdf#page=14). At that vacancy rate Canadian data shows that [rents are sure to drop](https://doodles.mountainmath.ca/blog/2018/11/28/vacancy-rate-and-rent-change/). By Vancouver standards, that vacancy rate is astronomically high. But that's not the experience people have on the street. So what gives?
```{r}
total_stock <- universe %>% filter(Year==2018) %>% pull(Total)
top_stock <- total_stock * 0.002
```
The key context for this number is that the is the $3,750 cutoff is [the 99.8 percentile rent level in the city](https://vancouver.ca/files/cov/2019-housing-vancouver-annual-progress-report-and-data-book.pdf#page=185), or roughly the `r round(top_stock)` most expensive units.
```{r}
bedrooms <- c("Bachelor","1 Bedroom","2 Bedroom","3 Bedroom +","Total")
bedroom_colors <- set_names(RColorBrewer::brewer.pal(length(bedrooms),"Dark2"),bedrooms)
caption <- "MountainMath, CMHC RMS custom tabulation"
quintile_break_labels <- c(
"Q1"="20 percentile",
"Q2"="40 percentile",
"Q3"="60 percentile",
"Q4"="80 percentile"
)
quintile_break_colors <- set_names(RColorBrewer::brewer.pal(4,"Set1"),as.character(quintile_break_labels))
d <- readxl::read_xlsx(here::here("data/vacancy-rate-universe-by-quintile-apt-city-vancouver-csd-2014-2018.xlsx"))
nn <- c("Zone","Bedrooms","Year",
"Q1_Universe_Value","Q1_Universe_Quality","Q1_VacancyRate_Value","Q1_VacancyRate_Quality",
"Q1_Value","Q1_Quality",
"Q2_Universe_Value","Q2_Universe_Quality","Q2_VacancyRate_Value","Q2_VacancyRate_Quality",
"Q2_Value","Q2_Quality",
"Q3_Universe_Value","Q3_Universe_Quality","Q3_VacancyRate_Value","Q3_VacancyRate_Quality",
"Q3_Value","Q3_Quality",
"Q4_Universe_Value","Q4_Universe_Quality","Q4_VacancyRate_Value","Q4_VacancyRate_Quality",
"Q4_Value","Q4_Quality",
"Q5_Universe_Value","Q5_Universe_Quality","Q5_VacancyRate_Value","Q5_VacancyRate_Quality",
"NonMarketUnknown_Universe_Value","NonMarketUnknown_Universe_Quality","NonMarketUnknown_VacancyRate_Value","NonMarketUnknown_VacancyRate_Quality")
rent_quintiles <- d %>%
slice(10:n()) %>%
set_names(nn) %>%
filter(!is.na(Bedrooms)) %>%
mutate(Bedrooms=factor(Bedrooms,levels = bedrooms))
rental_cutoffs <- rent_quintiles %>% select("Zone","Bedrooms","Year","Q1_Value","Q1_Quality","Q2_Value","Q2_Quality","Q3_Value","Q3_Quality","Q4_Value","Q4_Quality") %>%
pivot_longer(-one_of("Zone", "Bedrooms","Year"), names_to = c("Quintile break",".value"), names_sep = "_") %>%
mutate(Value=as.integer(Value)) %>%
#mutate(`Quintile break`=paste0(`Quintile break`,"/Q",as.integer(substr(`Quintile break`,2,2))+1))
mutate(`Quintile break`=quintile_break_labels[`Quintile break`]) %>%
mutate(`Quintile break`=factor(`Quintile break`,levels=as.character(quintile_break_labels)))
rental_data <- rent_quintiles %>% select(-one_of("Q1_Value","Q1_Quality","Q2_Value","Q2_Quality","Q3_Value","Q3_Quality","Q4_Value","Q4_Quality")) %>%
pivot_longer(-one_of("Zone", "Bedrooms","Year"), names_to = c("Quintile","Type",".value"), names_sep = "_") %>%
mutate(Value=as.numeric(Value)) %>%
mutate(Value=ifelse(Type=="VacancyRate",Value/100,Value))
```
Looking at the top 99.8 percentile is probably not very informative. Moreover, it's generally a good idea to at least slice the data by bedroom type, and if possible also by location. Let's take a look at rent quintiles by bedroom types in the City of Vancouver. That is we divide the purpose-built rental stock by bedroom type into five equally sized groups ordered by rent and check the vacancy rate in each of these groups.
```{r vacancy_by_quintile}
plot_data <- rental_data %>%
filter(Zone=="City of Vancouver (Zones 1-10)",
Year==2018,
Type=="VacancyRate",
Quintile!="NonMarketUnknown")
ggplot(plot_data,aes(x=Quintile,y=Value,fill=Bedrooms)) +
geom_bar(stat="identity",position="dodge") +
theme_light() +
scale_y_continuous(labels=scales::percent) +
scale_fill_manual(values=bedroom_colors) +
labs(title="City of Vancouver vacancy rate by rent quintile",x="Quintile",y="Vacancy rate", caption=caption)
```
The vacancy rate increases with the quintiles, with more expensive units being more likely to be empty. There are a number of reasons for this. The pool of people considering renting an expensive unit is naturally somewhat smaller than the pool of people considering to rent a unit priced in the middle quintiles, as there are fewer people able to pay those rents. But in Vancouver there are two other drivers that are more important for explaining the higher vacancy rates in the top quintile:
* Rent control leads to occupied units renting on average at below-market rents. Because of vacancy decontrol that changes the instant the unit turns vacant, and the "rent" for the vacant unit, that is the advertised rent, will be much higher than what the unit previously rented for. A while back we [looked into this in detail](https://doodles.mountainmath.ca/blog/2018/11/28/moving-penalty/). This means that vacant units will have higher rents than occupied ones, and will accordingly skew toward the higher quintiles.
* When new buildings come online, it takes time to fully tenant the building. This is analogous to [new condo buildings taking time to fill in](https://doodles.mountainmath.ca/blog/2017/04/03/joyce-collingwood/), although the process is much faster in rental buildings. But it still takes time, especially in a rent controlled environment where landlords are willing to wait a little longer to fill the units rather than lower rents that will get locked in. And new units are generally more expensive and likely to fetch rents in the higher quintiles.
Looking at the previous years, we see that there has been some variation in the vacancy rates across quintiles, with 2017 mirroring 2018 higher vacancy rates in the top quintile within each bedroom category.
```{r}
plot_data <- rental_data %>%
filter(Zone=="City of Vancouver (Zones 1-10)",
Year!=2018,
Type=="VacancyRate",
Quintile!="NonMarketUnknown")
ggplot(plot_data,aes(x=Quintile,y=Value,fill=Bedrooms)) +
geom_bar(stat="identity",position="dodge") +
theme_light() +
scale_y_continuous(labels=scales::percent) +
scale_fill_manual(values=bedroom_colors) +
facet_wrap("Year") +
labs(title="City of Vancouver vacancy rate by rent quintile",x="Quintile",y="Vacancy rate", caption=caption)
```
We also notice quality concerns that have lead to removal of vacancy rates for 3 bedroom units in various quintiles and years, indicating that we probably should be careful interpreting the 3 bedroom vacancy rates.
```{r}
rent_3brm_q5 <- rental_cutoffs %>% filter(Year==2018, Bedrooms=="3 Bedroom +",`Quintile break`=="80 percentile",Zone=="City of Vancouver (Zones 1-10)") %>% pull(Value)
```
To put that into relation to the numbers from the Vancouver Housing Data Book we take a look at the rent levels that break the rents into quintiles, and how these have evolved.
```{r}
plot_data <- rental_cutoffs %>% filter(Zone=="City of Vancouver (Zones 1-10)",Bedrooms!="Total")
ggplot(plot_data,aes(x=Year,y=Value,color=`Quintile break`,group=`Quintile break`)) +
geom_line() +
geom_point(aes(shape=Quality)) +
scale_shape_discrete(labels=c("a"="Excellent",b="Very Good",c="Good",d="Use with caution")) +
facet_wrap("Bedrooms") +
theme_light() +
scale_color_manual(values=quintile_break_colors) +
scale_y_continuous(labels=scales::dollar) +
labs(title="City of Vancouver rent quintile cutoffs",caption=caption,
y="Quintile cutoff rent",shape="Data Quality")
```
We again notice quality issues in the 3 bedroom rent cutoffs, due to the low number of 3 bedroom units in the universe. The lower cutoff of the top quintile 3 Bedroom rent in 2018 was `r scales::dollar(rent_3brm_q5)`, a bit below the overall 99.8 percentile cutoff of $3,750 reported in the Vancouver Housing Data Book.
In summary, it seldom makes sense to report rent levels without first slicing at least by bedroom type. And it is more useful to slice by smaller groups like quintiles instead of reporting values for the top 0.2% rent level.
## Geographic distribution
```{r}
cov_clip <- get_census("CA16",regions=list(CSD=c("5915803","5915022")),geo_format = "sf")
zones <- get_cmhc_geography(level="ZONE") %>%
filter(MET_CODE=="2410",ZONE_NAME_EN %in% (rental_data$Zone %>% unique))
bbox_vt = zones %>% sf::st_transform(4236) %>% sf::st_bbox()
vector_tiles <- mountainmathHelpers::get_vector_tiles(bbox_vt)
roads <- rmapzen::as_sf(vector_tiles$roads) %>% filter(kind != "ferry")
water <- rmapzen::as_sf(vector_tiles$water)
bbox <- zones %>% sf::st_bbox()
```
Next to bedrooms, geography is the other important variable to consider. Most renters care about roughly where in the city they are renting. For example, here are the vacancy rates for 1 Bedroom apartments in the first rent quintile by survey zone. The rent quintiles are taken over the entire City of Vancouver stock.
```{r}
plot_data <- zones %>%
inner_join(rental_data,by=c("ZONE_NAME_EN"="Zone")) %>%
filter(`Bedrooms`=="1 Bedroom",Quintile=="Q1",Type=="VacancyRate")
ggplot(plot_data) +
geom_sf(data=roads,size=0.1,color="darkgrey",fill=NA) +
geom_sf(aes(fill=Value),size=0.5) +
geom_sf(data = water, fill = "lightblue", colour = NA) +
facet_wrap("Year") +
scale_fill_viridis_c(labels=scales::percent) +
coord_sf(datum=NA,
xlim=c(bbox$xmin,bbox$xmax),
ylim=c(bbox$ymin,bbox$ymax)) +
labs(title="1 Bedroom first rent quintile vacancy rate",
fill="Vacancy rate",
caption="MountainMath, CMHC Rms custom extract")
```
We see a large variation in the vacancy rate of 1 Bedroom units in the first city-wide rent quintile across survey zones. Part of that volatility can be explained by some survey zones having only relatively small number of units in the first rent quintile, as the following map shows.
```{r}
plot_data <- zones %>%
inner_join(rental_data,by=c("ZONE_NAME_EN"="Zone")) %>%
filter(`Bedrooms`=="1 Bedroom",Quintile=="Q1",Type=="Universe")
ggplot(plot_data) +
geom_sf(data=roads,size=0.1,color="darkgrey",fill=NA) +
geom_sf(aes(fill=Value),size=0.5) +
geom_sf(data = water, fill = "lightblue", colour = NA) +
facet_wrap("Year") +
scale_fill_viridis_c(labels=scales::comma,option = "inferno") +
coord_sf(datum=NA,
xlim=c(bbox$xmin,bbox$xmax),
ylim=c(bbox$ymin,bbox$ymax)) +
labs(title="Number of 1 Bedroom units in first rent quintile",
fill="# Units",
caption="MountainMath, CMHC Rms custom extract")
```
The number of units is suppressed in some regions for some years due to quality issues. Marpole has the highest number of 1 bedroom units in the lowest rent quintile, whereas the downtown peninsula has relatively few, given the large number of 1 bedroom units there.
```{r}
metro_region_params=cmhc_region_params(geography = "Vancouver",type="CMA")
get_universe_for_year <- function(year){
params <- cmhc_snapshot_params(table_id = "2.1.26.3",region=metro_region_params,month="10",year=year)
get_cmhc(params) %>%
rename(Zone=X1) %>%
select(-X2) %>%
mutate(Year=as.character(year))
}
universe_zones <- lapply(seq(2014,2018),get_universe_for_year) %>% bind_rows
```
To confirm this last point, we plot the total number of 1 Bedroom units in each survey zone.
```{r}
plot_data <- zones %>%
inner_join(universe_zones,by=c("ZONE_NAME_EN"="Zone")) %>%
inner_join(rental_data,by=c("ZONE_NAME_EN"="Zone","Year"="Year")) %>%
filter(`Bedrooms`=="1 Bedroom",Quintile=="Q1",Type=="Universe") %>%
mutate(Share=Value/`1 Bedroom`)
ggplot(plot_data) +
geom_sf(data=roads,size=0.1,color="darkgrey",fill=NA) +
geom_sf(aes(fill=`1 Bedroom`),size=0.5) +
geom_sf(data = water, fill = "lightblue", colour = NA) +
facet_wrap("Year") +
scale_fill_viridis_c(labels=scales::comma,option = "inferno") +
coord_sf(datum=NA,
xlim=c(bbox$xmin,bbox$xmax),
ylim=c(bbox$ymin,bbox$ymax)) +
labs(title="Number of 1 Bedroom units",
fill="# Units",
caption="MountainMath, CMHC Rms custom extract")
```
The share of the 1 bedroom units that are in the first rent quintile complete this picture.
```{r}
ggplot(plot_data) +
geom_sf(data=roads,size=0.1,color="darkgrey",fill=NA) +
geom_sf(aes(fill=Share),size=0.5) +
geom_sf(data = water, fill = "lightblue", colour = NA) +
facet_wrap("Year") +
scale_fill_viridis_c(labels=scales::percent,option = "inferno") +
coord_sf(datum=NA,
xlim=c(bbox$xmin,bbox$xmax),
ylim=c(bbox$ymin,bbox$ymax)) +
labs(title="Share of 1 Bedroom units in first rent quintile",
fill="Share",
caption="MountainMath, CMHC Rms custom extract")
```
On the flip side, where can plot the share of 1 bedroom units in the top rent quintile in each regions.
```{r}
plot_data <- zones %>%
inner_join(universe_zones,by=c("ZONE_NAME_EN"="Zone")) %>%
inner_join(rental_data,by=c("ZONE_NAME_EN"="Zone","Year"="Year")) %>%
filter(`Bedrooms`=="1 Bedroom",Quintile=="Q5",Type=="Universe") %>%
mutate(Share=Value/`1 Bedroom`)
ggplot(plot_data) +
geom_sf(data=roads,size=0.1,color="darkgrey",fill=NA) +
geom_sf(aes(fill=Share),size=0.5) +
geom_sf(data = water, fill = "lightblue", colour = NA) +
facet_wrap("Year") +
scale_fill_viridis_c(labels=scales::percent,option = "inferno") +
coord_sf(datum=NA,
xlim=c(bbox$xmin,bbox$xmax),
ylim=c(bbox$ymin,bbox$ymax)) +
labs(title="Share of 1 Bedroom units in top rent quintile",
fill="Share",
caption="MountainMath, CMHC Rms custom extract")
```
This underscores the relative attractiveness of central (and West Side) as opposed to Marpole (and East Side) locations..
## Upshot
Looking at the very extremes of the rent quintiles is likely not going to generate much insight, it will be clouded by data quality issues and not have much relevance for broader questions. As usual, the code for this post is [available on GitHub](https://github.com/mountainMath/doodles/blob/master/content/posts/2019-10-16-rents-and-vacancy-rates.Rmarkdown) for those interested playing further with the numbers. The CMHC Rms custom extract is [also available on GitHub](https://github.com/mountainMath/doodles/raw/master/data/vacancy-rate-universe-by-quintile-apt-city-vancouver-csd-2014-2018.xlsx).
You can’t perform that action at this time.