Interactive choropleth maps with R and tmap (part I)

In this post, I would like to share how to prepare an interactive map using #rstats and {tmap} package. The first part shows how to make a simple (one thematic layer), interactive choropleth map and save an output as a .html file, which can be then inserted to a website or serve as a stand alone web. A {tmap} provides with a certain interactivity: zoom in/out and map moving, a popup display and a selection of visibility of basemaps and/or thematic layers.

The map shows an total number of accessible jobs from a given location (transport zone), applying a typical commuting distance of inhabitants of Metropolitan area of Madrid (more details about used potential accessibility measure, can be found here).

Data used for this example can be downloaded from the github. The accessibility values were calculated for the MSCA CALCULUS project. The shapefiles of transport zones of Madrid can be downloaded from the open data portal of the Comunidad de Madrid (Delimitaciones Territoriales / Zonas de Transporte). In this map I use the zones dated for year 2013 limited to those located within the Municipality of Madrid (584 units).

Step 1. Load packages and get the data

library(readr)
library(dplyr)
library(tmap)
library(rgdal)

For the map we need a shapefile with transport zones of Madrid (Madrid_TAZ.shp) and csv file with accessibility values, both stored in the [Data] subfolder. The code below loads a shapefile (as SpatialPolygonsDataFrame) and merges it with the .csv. Additionally, the code recalculates the total number of accessible jobs (an original Ai_tmap.csv files contains results in thousands) which will be used in the popup.

Free.Flow <- merge(
      
      # read shapefile
      readOGR("Data/Madrid_TAZ.shp",
            layer = "Madrid_TAZ", GDAL1_integer64_policy = TRUE),
      
      # read csv file add a new column to be displayed in popup
      (read_csv("Data/Ai_tmap.csv") %>%
            mutate(K.FreeFlow = FreeFlow*1000) ),
      
      # matching columns
      by.x = "TAZ_Madr_1", by.y = "Or") 

Step 2. Make a (t)map!

First, let’s set the tmap “view” mode:

tmap_mode("view")

The advantage of using tmap is that it shares a logic with ggplot. Thus, first we need to create a tmap object (tm_shape()) followed by a thematic layer (tm_polygons()), and then additional parameters (e.g. tm_layout()) should be specified. The subsequent elements we add using a +.

Let’s start with the first map using all default settings. We need to define the source of thematic layer (Free.Flow - a SpatialPolygonsDataFrame object) and a variable which defines a thematic layer (FreeFlow):

tm_shape(Free.Flow) +
    tm_polygons("FreeFlow")

Here it is! A first interactive map. In the top-left corner, we can turn off the visibility of our thematic layer and select one of the three default basemaps.

The popup shows us a value taken from the column used to create a thematic layer (FreeFlow) and it uses a first column to define area ID. We can change it now, using a popup.vars parameter of tm_polygons() function. The content of the popup is defined as: some.text = variable.name and a variable.name should be matched to the name of column of a thematic layer (as defined by tm_shape()). We use two columns: K.FreeFlow which contains a total number of available jobs, and POP2017 indicating a total number of population:

tm_shape(Free.Flow) +
    tm_polygons("FreeFlow",
                # popup definition
                popup.vars=c(
                    "Accessible_jobs: "="K.FreeFlow",
                    "Population: " = "POP2017")  
                )

One remark: by default the popup uses a first column of our SpatialPolygonsDataFrame to display an area ID. We can use variables from another column by defining an id parameter:

tm_shape(Free.Flow) +
      tm_polygons("FreeFlow",
                    
                    # popup definition
                    popup.vars=c(
                            "Accessible_jobs: "="K.FreeFlow",
                            "Population: " = "POP2017"),
                    id = "TAZ_Madrid"
        )

Now, let’s change the color palette and transparency of the thematic layer. I want to use the inferno palette - a viridis-based palette matched by {colorspace} team. I want to divide my data into as much as 16 classes. Additionally, I want to set the transparency of the thematic layer. In result, I need to define three parameters: palette, n (number of classes) and alpha (transparency):

        alpha = 0.6,
        n = 16,
        palette = hcl.colors(16, palette = "Inferno")

However, the darkest colors of the inferno palette are a bit too dark for me, so I want to exclude them. In order to do this, first I set more colors from the palette, and then filter out first two of them. Now, our code looks like this:

tm_shape(Free.Flow) +
        tm_polygons("FreeFlow",
                    
                    # popup definition
                    popup.vars=c(
                            "Accessible_jobs: "="K.FreeFlow",
                            "Population: " = "POP2017"),
                    id = "TAZ_Madrid",
                    
                    # transparency, number of classes and palette
                    alpha = 0.6,
                    n = 16,
                    palette = hcl.colors(18, palette = "Inferno")[3:18]
        )

The zones’ borders are too visible so we need to increase their transparency and change their color. I’ve tested several solutions taking an advantage of this on-line application and selected the one that fits the best:

tm_shape(Free.Flow) +
        tm_polygons("FreeFlow",
                    
                    # popup definition
                    popup.vars=c(
                            "Accessible_jobs: "="K.FreeFlow",
                            "Population: " = "POP2017"),
                    id = "TAZ_Madrid",
                    
                    # transparency, number of classes and palette
                    alpha = 0.6,
                    n = 16,
                    palette = hcl.colors(18, palette = "Inferno")[3:18],
                    
                    # border definition: color and transparency
                    border.col = "#990099",
                    border.alpha = 0.1
        )

The map is almost ready. The last amendments: title of the legend and the map title. The previous is defined by the title parameter of tm_polygons() function, while the latter by a parameter of a separate function tm_layout():

tm_shape(Free.Flow) +
        tm_polygons("FreeFlow",
                    
                    # popup definition
                    popup.vars=c(
                            "Accessible_jobs: "="K.FreeFlow",
                            "Population: " = "POP2017"),
                    id = "TAZ_Madrid",
                    
                    # transparency, number of classes and palette
                    alpha = 0.6,
                    n = 16,
                    palette = hcl.colors(18, palette = "Inferno")[3:18],
                    
                    # border definition: color and transparency
                    border.col = "#990099",
                    border.alpha = 0.1, 
                    
                    # title of the legend
                    title = "Accessible jobs<br>(thous.)"
        ) +
    
    # map title 
    tm_layout(title = "Accessibility to jobs<br>Model: Car free flow speeds")

Note, that in both cases, a typical html tags (like <br>) can be used.

Step 3. Save and re-use the output

The map is ready! Now, we only need to save it as a html widget. We can use a native tmap_save() function:

tmap_last() %>% 
    tmap_save("Madrid_accessibility_map.html")

It can be then inserted to a web page:

<iframe src="/img/Madrid_accessibility_map.html" frameborder=0,  height=400, width="100%", scrolling="no"></iframe>

All the code and the data can be downloaded from my github.


Session info:

R version 3.6.0 (2019-04-26)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Mojave 10.14.5


Some useful links:



Avatar
Marcin Stępniak
Marie Curie postdoc researcher

I am transport and urban geographer interested in accessibility analysis, GTFS data and R scripts.

Related