Datasets to identify Affordable and Moderately Priced housing in Montgomery County

Annual Rental Facility Survey

2021 Annual Rental Facility Occupancy Survey Facilities includes the average reported rent by bedroom count for Montgomery County facilities.

https://data.montgomerycountymd.gov/Consumer-Housing/2021-Rental-Facility-Occupancy-Survey-Results/7wpt-giig

rents <- read_csv("2021_Rental_Facility_Occupancy_Survey_Results.csv")
names(rents) <- str_to_lower(str_replace_all(names(rents), ' ','_'))
glimpse(rents)
## Rows: 1,550
## Columns: 11
## $ community_name                                <chr> "8513 Flower", "Central"~
## $ community_address                             <chr> "8513 FLOWER AVE TAKOMA ~
## $ bedroom_types                                 <chr> "1 bedroom", "2 bedroom"~
## $ average_rent_2016                             <chr> NA, NA, NA, NA, "$   875~
## $ average_rent_2017                             <chr> NA, NA, "$   650", NA, "~
## $ average_rent_2018                             <chr> NA, "$   2,624", "$   65~
## $ average_rent_2019                             <chr> "$   800", "$   2,637", ~
## $ average_rent_2020                             <chr> "$   895", "$   2,644", ~
## $ average_rent_2021                             <chr> "$   895", "$   2,653", ~
## $ `percent_change_from_previous_year_2020-2021` <dbl> 0.0000, 0.0032, 0.0000, ~
## $ geo_location                                  <chr> NA, NA, NA, NA, NA, NA, ~
cat('\nnrows = ', nrow(rents),
    '\nunique addresses = ', length(unique(rents$community_address)),
    '\nunique community names', length(unique(rents$community_name)))
## 
## nrows =  1550 
## unique addresses =  706 
## unique community names 703

Filter the data for the 2021 rent information.

#Select the name, address, bedroom type, 2021 average rent and the geo-location fields

rents_2021 <- rents %>% select(starts_with(c('comm', 'bed')), average_rent_2021, geo_location)

#Create an integer bedroom count field from the first character of bedroom_type, assign 0 to efficiency 

rents_2021$bedroom_count <- str_sub(rents_2021$bedroom_types, 1,1) %>% str_replace('e', '0') %>% as.integer()

#Convert the average rent field to an integer and sort the data by bedroom count and average rent

rents_2021$average_rent_2021 <-  as.integer(gsub("[$,]", "", rents_2021$average_rent_2021))
rents_2021 <- arrange(rents_2021, bedroom_count, average_rent_2021)

head(rents_2021)
## # A tibble: 6 x 6
##   community_name    community_addre~ bedroom_types average_rent_20~ geo_location
##   <chr>             <chr>            <chr>                    <int> <chr>       
## 1 7133 Carroll Ave~ 7133 CARROLL AV~ efficiency                 500 <NA>        
## 2 7118 Carroll Ave~ 7118 CARROLL AV~ efficiency                 560 <NA>        
## 3 7126 Carroll Ave~ 7126 CARROLL AV~ efficiency                 577 <NA>        
## 4 1001 University ~ 1001 UNIVERSITY~ efficiency                 586 <NA>        
## 5 Cambridge Apartm~ 676 HOUSTON AVE~ efficiency                 645 <NA>        
## 6 8608 Flower Aven~ 8608 FLOWER AVE~ efficiency                 647 <NA>        
## # ... with 1 more variable: bedroom_count <int>
#Look at the summary data for 2021 rents
summary(rents_2021)
##  community_name     community_address  bedroom_types      average_rent_2021
##  Length:1550        Length:1550        Length:1550        Min.   :  155    
##  Class :character   Class :character   Class :character   1st Qu.: 1127    
##  Mode  :character   Mode  :character   Mode  :character   Median : 1422    
##                                                           Mean   : 1601    
##                                                           3rd Qu.: 1778    
##                                                           Max.   :13798    
##  geo_location       bedroom_count  
##  Length:1550        Min.   :0.000  
##  Class :character   1st Qu.:1.000  
##  Mode  :character   Median :2.000  
##                     Mean   :1.565  
##                     3rd Qu.:2.000  
##                     Max.   :4.000
rents_2021 %>% group_by(bedroom_count) %>% summarise(min(average_rent_2021), max(average_rent_2021))
## # A tibble: 5 x 3
##   bedroom_count `min(average_rent_2021)` `max(average_rent_2021)`
##           <int>                    <int>                    <int>
## 1             0                      500                     8211
## 2             1                      166                    11442
## 3             2                      155                    13798
## 4             3                      600                     6391
## 5             4                     1138                     6325

Look at the spread of rents by bedroom counts.

rp <- ggplot(rents_2021,aes(x=bedroom_count, y=average_rent_2021, fill = factor(bedroom_count))) +
  geom_boxplot() +
  scale_x_continuous(n.breaks = 6)
rp

There are large numbers of high outliers. The highest rents are for apartments at a senior living facility in Kensington that provides meal, assisted living and medical services. The lowest rents are less than $200 a month for 1 or 2 bedroom apartments in Gaithersburg and Takoma Park. These may be subsidized housing or data entry errors.

Affordable rents

According to the Montgomery County Department of Housing and Community Affairs annual report Montgomery County currently has more than 20,000 households who are severely housing cost burdened; they earn less than $31,000/ year and spend more than half their income on rent. And more than 80 percent of renters earning up to $70,000/year are housing cost burdened. https://www.montgomerycountymd.gov/DHCA/Resources/Files/director/publications/dhca_annual_report-fy20-21.pdf

HUD defines housing cost burden as over 30% of income spent on housing. https://www.huduser.gov/portal/pdredge/pdr-edge-featd-article-081417.html

#Filter for rents that are <= to 30% of a 70k income

affordable <- (70000 * .3) / 12
affordable_rents <- rents_2021 %>% 
  filter(average_rent_2021 <= affordable)

#Look at the summary data for affordable rents
summary(affordable_rents)
##  community_name     community_address  bedroom_types      average_rent_2021
##  Length:1150        Length:1150        Length:1150        Min.   : 155     
##  Class :character   Class :character   Class :character   1st Qu.:1047     
##  Mode  :character   Mode  :character   Mode  :character   Median :1273     
##                                                           Mean   :1257     
##                                                           3rd Qu.:1494     
##                                                           Max.   :1750     
##  geo_location       bedroom_count 
##  Length:1150        Min.   :0.00  
##  Class :character   1st Qu.:1.00  
##  Mode  :character   Median :1.00  
##                     Mean   :1.37  
##                     3rd Qu.:2.00  
##                     Max.   :4.00
affordable_rents %>% group_by(bedroom_count) %>% summarise(min(average_rent_2021), max(average_rent_2021))
## # A tibble: 5 x 3
##   bedroom_count `min(average_rent_2021)` `max(average_rent_2021)`
##           <int>                    <int>                    <int>
## 1             0                      500                     1743
## 2             1                      166                     1749
## 3             2                      155                     1746
## 4             3                      600                     1750
## 5             4                     1138                     1719
affordable_pct <- round(nrow(affordable_rents) / nrow(rents_2021), 2) * 100

The affordable threshold for for an income of $70,000/year is 1750 a month. 74 percent of the rents in the dataset are affordable. But only shows where an apartment is available at a particular rent, not how many affordable apartments there are.

rp <- ggplot(affordable_rents,aes(x=bedroom_count, y=average_rent_2021, 
                                  fill=factor(bedroom_count))) +
  geom_boxplot() +
  scale_x_continuous(n.breaks = 6)
rp

Facility geo-location data

geo_loc <- affordable_rents %>% filter(is.na(geo_location)) %>% distinct(community_name, community_address, geo_location)
filter(geo_loc, is.na(community_address))
## # A tibble: 2 x 3
##   community_name           community_address geo_location
##   <chr>                    <chr>             <chr>       
## 1 Boulevard Of Chevy Chase <NA>              <NA>        
## 2 Charter House            <NA>              <NA>

There are 424 facilities missing geo-location information, the majority can be geo-coded using the address field. Two facilities have missing addresses. The address for Charter House is 1316 Fenwick Lane Silver Spring, MD 20910. An obvious address doesn’t show up in a search for Boulevard of Chevy Chase.

affordable_rents[affordable_rents$community_name == 'Charter House', "community_address"] <- 
  '1316 Fenwick Lane Silver Spring, MD 20910'

Moderately Priced Dwelling Units data

The Moderately Priced Dwelling Units (MPDU) data set gives an inventory of affordable housing by county planning area. https://data.montgomerycountymd.gov/Consumer-Housing/Moderately-Priced-Dwelling-Units/pbh5-g4e5.

mpdu_in <- read_csv("Moderately_Priced_Dwelling_Units.csv")
names(mpdu_in) <- str_to_lower(str_replace_all(names(mpdu_in), ' ','_'))
glimpse(mpdu_in)
## Rows: 608
## Columns: 6
## $ planning_area    <chr> "PA34 - Fairland", "N/A", "PA25 - Travilah", "PA36 - ~
## $ total_units      <dbl> 102, 325, 131, 135, 1680, 64, 52, 293, 68, 71, 94, 0,~
## $ total_mpdu       <dbl> 16, 41, 17, 19, 266, 8, 42, 44, 9, 15, 29, 60, 266, 1~
## $ total_workforce  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
## $ rental_indicator <chr> "For-Sale & Rental", "For-Sale & Rental", "For-Sale &~
## $ offering_date    <chr> NA, "05/01/2006", "09/01/2016", NA, "01/01/2012", "03~

The rental_indicator is the same value for all rows and the total_workforce field has only 14 rows with values. There separate rows for offering_date that result in duplicate data for total-units and total_mpdu.

# select fields and rows total_mpdu values > 0

mpdu <- mpdu_in %>% select(planning_area, total_units, total_mpdu, offering_date) %>% 
  filter(total_mpdu !=0 & !is.na(total_mpdu)) %>% 
  arrange(planning_area, desc(offering_date))
summary(mpdu)
##  planning_area       total_units       total_mpdu     offering_date     
##  Length:456         Min.   :   0.0   Min.   :  2.00   Length:456        
##  Class :character   1st Qu.:  74.0   1st Qu.: 13.00   Class :character  
##  Mode  :character   Median : 180.0   Median : 28.00   Mode  :character  
##                     Mean   : 270.4   Mean   : 41.09                     
##                     3rd Qu.: 341.0   3rd Qu.: 55.00                     
##                     Max.   :2703.0   Max.   :405.00                     
##                     NA's   :13
# select distinct rows to eliminate duplicates by offering date

mpdu <- mpdu %>% distinct(planning_area, total_units, total_mpdu, .keep_all = TRUE)

There are 20 distinct planning_area values in the data, one of which is N/A. 12 rows have N/A in total_units.

mpdu %>% count(total_units == 0 | is.na(total_units))  # How many rows have total_units = 0 or null?
## # A tibble: 2 x 2
##   `total_units == 0 | is.na(total_units)`     n
##   <lgl>                                   <int>
## 1 FALSE                                     271
## 2 TRUE                                       31
mpdu$pct_mpdu <-  if_else(mpdu$total_units == 0,0, mpdu$total_mpdu / mpdu$total_units) 

mpdu %>% group_by(planning_area) %>% 
  summarise(n=n(), total_units = sum(total_units, na.rm=TRUE), total_mpdu=sum(total_mpdu, na.rm = TRUE),
            pct_mpdu = sum(pct_mpdu, na.rm = TRUE)) %>%  
  kable() %>% kable_minimal()
planning_area n total_units total_mpdu pct_mpdu
N/A 88 19628 3777 11.1000016
PA11 - Damascus 5 590 95 0.7688291
PA13 - Clarksburg 20 8518 1176 2.5632427
PA19 - Germantown 24 4885 827 2.8690427
PA20 - Gaithersburg & Vicinity 14 3529 538 1.9453898
PA21 - Gaithersburg 1 0 57 0.0000000
PA22 - Upper Rock Creek 3 299 48 0.4952863
PA23 - Olney 7 820 165 1.3017286
PA24 - Darnestown 2 73 10 0.2761905
PA25 - Travilah 3 724 96 0.4465518
PA26 - Rockville 2 80 30 0.3000000
PA27 - Aspen Hill 6 333 55 1.0070358
PA28 - Cloverly 5 243 35 0.7370137
PA29 - Potomac 8 1294 228 0.9446885
PA30 - North Bethesda 21 6712 857 2.5475778
PA31 - Kensington/Wheaton 16 3571 498 2.4036664
PA33 - White Oak 2 128 20 0.3327616
PA34 - Fairland 9 896 141 1.1244892
PA35 - Bethesda/Chevy Chase 35 6782 971 5.9775355
PA36 - Silver Spring 31 6930 1084 5.0635589
total_pct_mpdu = (sum(mpdu$total_mpdu, na.rm = TRUE) / sum(mpdu$total_units, na.rm = TRUE)) * 100

cat('\nTotal PCT MPDU = ', total_pct_mpdu)
## 
## Total PCT MPDU =  16.21564

There are some obvious problems with the data, Rockville certainly has more than 80 rental units, and PA21 - Gaithersburg must have more than 0 total_units if there are 57 mpdu.