This visualisation presents housing prices in Singapore and has two parts. The first visualises housing prices by planning area for various property types. The second visualises housing price increases over the past ten years by planning area.
Obtaining data and wrangling. Property price data will be required and will need to be merged with map data. Historical housing transactions will be downloaded from REALIS. The columns which contain the information required are unit price ($ psf), sale date, property type and planning area. The data challenge will be to filter, aggregate and wrangle the data into different dataframes required for visualisation.
A design challenge would be to visualise housing prices by planning area effectively. Prices differ significantly by property types. Thus it would be desirable to also visualise how housing prices also vary with property type. A choropleth map with facet views for 4 property types (Apartment, Condominium, Executive Condominium and Landed House) would be prepared to address this challenge.
Another design challenge would be to visualize the change of the property prices. There were two main challenges to this. First, property prices changes slightly each year and this gradual change would not be obvious or could be noisy. The 10 year housing price change was selected as the appropriate period for visualisation. Secondly, the price increases should be presented as a percentage for a meaningful comparison across areas. The absolute housing price should also be presented simultaneously to enable comparison. An interactive choropleth and bubble map was prepared to reveal the price and price change of housing by planning area simultaneously.
Install and run tidyverse, tmap and sf.
packages = c('sf', 'tmap', 'tidyverse')
for (p in packages){
if(!require(p, character.only = T)){
install.packages(p)
}
library(p,character.only = T)
}
mpsz <- st_read(dsn = "data/geospatial",
layer = "MP14_SUBZONE_WEB_PL")
prop <- read_csv("data/aspatial/PropPrice.csv")
The first step involved extraction of year from property transaction date followed by filtering property transactions for 2019 from the REALIS dataset. The data columns of Unit Price (dollars psf), Property Type, Planning Region and Planning Area were selected. The categories under ‘Property Type’ were then amalgamated into 1 of 4 categories of Apartment, Condominium, Executive Condominium and Landed House. Finally the average price was calculated for each housing category, by planning area and joined with map shapefile data.
prop2019<-prop %>%
mutate(`Sale Date` = as.Date(`Sale Date`,'%d-%b-%Y'))%>%
mutate(`Year`= as.numeric(format(`Sale Date`, "%Y"))) %>%
filter(Year==2019) %>%
select(`Unit Price ($ psf)`,`Property Type`,`Planning Region`,`Planning Area`)%>%
mutate(`Property Type` = ifelse(`Property Type` == "Semi-Detached House"| `Property Type` =="Detached House"| `Property Type` =="Terrace House" ,"Landed House", `Property Type`))%>%
group_by(`Planning Area`,`Planning Region`,`Property Type`)%>%
summarise(mean_psf = mean(`Unit Price ($ psf)`, na.rm = TRUE))%>%
spread(`Property Type`,`mean_psf`)%>%
mutate_at(.vars = vars(`Planning Area`,`Planning Region`), toupper)
mpsz_prop2019 <- left_join(mpsz, prop2019,
by = c("PLN_AREA_N" = "Planning Area"))
The static plot mode for tmap is used. The tm_fill layer is specified with a 4 columns of price data, to display the facet view for each of the 4 housing types. The tm_layout layer was used to customise settings for the title and legend.
tmap_mode("plot")
tm_shape(mpsz_prop2019)+
tm_fill(c("Apartment", "Condominium","Executive Condominium", "Landed House"),
title = c("Apartment $psf", "Condo $psf","Executive Condo $psf", "Landed House $psf"),
style = "pretty",
palette = "Reds") +
tm_layout(main.title = "Property Price ($psf) by Planning Area",
main.title.position = "center",
main.title.size = 1.2,
frame = TRUE) +
tm_layout(legend.height = 0.45,
legend.width = 0.35,
legend.outside = FALSE,
legend.position = c("right","bottom"),
frame = FALSE
) +
tm_borders(alpha = 0.5) +
tmap_style("white")
The data was first filtered for property transactions in 2009 and 2019. The columns of unit price, planning region, planning area and year were then selected and grouped by planning area, planning region and year. The mutate function was used to calculate and create a new column for the housing price change in percentage by area.
prop0919<-prop %>%
mutate(`Sale Date` = as.Date(`Sale Date`,'%d-%b-%Y'))%>%
mutate(`Year`= as.numeric(format(`Sale Date`, "%Y"))) %>%
filter(Year==2019 | Year ==2009) %>%
select(`Unit Price ($ psf)`,`Planning Region`,`Planning Area`,`Year`)%>%
group_by(`Planning Area`,`Planning Region`,`Year`)%>%
summarise(mean_psf = mean(`Unit Price ($ psf)`, na.rm = TRUE))%>%
spread(`Year`,`mean_psf`)%>%
mutate(`Percentage Change` = `2019`/`2009`*100)%>%
mutate_at(.vars = vars(`Planning Area`,`Planning Region`), toupper)
mpsz_prop0919 <- left_join(mpsz, prop0919,
by = c("PLN_AREA_N" = "Planning Area"))
The tmap view mode was selected for interactive visualisation. A tm_fill layer was added to build the background choropleth map visualing property price by area. A tm_bubble layer was also added to create a bubble map stacked on top of the choropleth map to visualise percentage change by bubble size and colour. The pop.vars command was used to define the information to be displayed within the pop-up box.
tmap_mode("view")
tm_shape(mpsz_prop0919)+
tm_fill("2019",
title="Dollars, per sq foot",
style = "pretty",
palette = "Reds",
popup.vars=c("Planning Area"="PLN_AREA_N","Subzone"="SUBZONE_N","2019 Average $psf"="2019", "2009 Average $psf"="2009","Price Increase(%)"="Percentage Change" )
) +
tm_bubbles("Percentage Change",
col="Percentage Change",
#title="Price Change from 2009 to 2019 (%)",
alpha=0.8,
scale=.5,
style="pretty",
palette = "Blues",
popup.vars=c("Planning Area"="PLN_AREA_N","Subzone"="SUBZONE_N","2019 Average $psf"="2019", "2009 Average $psf"="2009","Price Increase(%)"="Percentage Change" )
)+
tm_view(view.legend.position = c("right","bottom"))+
tm_scale_bar(width = 0.15) +
tm_borders(alpha = 0.5)
The following set of four choropleth maps visualises the housing prices by planning area for 4 categories of housing. These maps reveal the following insights. Apartments and condominiums are most expensive in the southern and central areas. The least expensive areas for these type of housing are in the north and eastern areas. These property types also have the largest dollars per square foot (psf) price range ranging from $500-$4000 psf. There are relative few areas with executive condominiums and they are the most expensive in Bishan. The price range for this property type is relatively narrow, ranging from $600-$1200 psf. Landed houses are most expensive in the areas of Tanglin and Novena and are the least expensive in Punggol. The price range for landed houses are between $500-$3000 psf.
The following interactive map visualises the percentage price changes of housing price by planning area over the period from 2009 to 2019. The colour of the planning areas represent the 2019 price psf while the size and colour of bubbles visualise the magnitude of price change for the area. The map may be panned and zoomed. Clicking on various areas of the map reveals the average price in $psf in 2009 and 2009, percentage price change for the planning area during the period and the name of the planning area and subzone of the area explored.
The visualisation reveals that the most expensive city areas like orchard and downtown core experienced roughly median price increases of around 150% over the past 10 years. The area which had the largest property price increase was Punggol with an average psf increase of 377%. However, Punggol remains relatively inexpensive compared with other areas today with housing costing an average of $1000-$1500psf.
Property Sales Data was obtained from Real Estate Information System (REALIS) - URA.