htmlwidgets - leaflet

htmlwidgets - leaflet #

The Bigger Picture #

In this document we learn how to create interactive charts with leaflet. Simply put, we are learning how to transform tidy data into visually clear graphs. In the overall context of the workflow, this falls into the category of transforming our data into data visualisation.

 

What is Leaflet? #

LinkedIn Learning 2.1

library("leaflet")
  • An htmlwidget used to make interactive maps
  • The package is bound to the Leaflet library in JavaScript
  • Can create:
    • Geoplots
    • Choropleths
    • Geolines maps
  • Like the Tidyverse, leaflet heavily depends on the pipeline (%>%) operator to create charts

Creating basic geoplots #

LinkedIn Learning 2.2, 2.3

The example data used is from the ACORN-SAT dataset. The dataset spans 112 Australian stations recording temperature measurements for over 100 years. We will use data from the year 2000.

station_data %>%
  sample_n(5)
##   Number year average.temp Station.name Latitude Longitude Elevation Start
## 1  36007 2000         23.0   Barcaldine   -23.55    145.29       266  1962
## 2   7045 2000         21.5  Meekatharra   -26.61    118.54       517  1926
## 3  53115 2000         19.3        Moree   -29.49    149.85       213  1912
## 4   9518 2000         17.5 Cape Leeuwin   -34.37    115.14        13  1910
## 5  14015 2000         27.3       Darwin   -12.42    130.89        30  1910
  • We begin by piping our data into the leaflet() function to begin any leaflet chart
station_data %>%
  leaflet()
  • We may then pipe this data into a variety of “functions” to customise the graph we create
  • Since we first want a geoplot, we use addTiles() to get a default map
plot <- station_data %>%
  leaflet() %>%
  addTiles()
widgetframe::frameWidget(plot)

Notice how we can move around and zoom in – Leaflet is very interactive!

plot <- station_data %>%
  leaflet() %>%
  addProviderTiles(providers$Esri.NatGeoWorldMap)
widgetframe::frameWidget(plot)
  • We may now pipe into addMarkers() to place our geographical data
station_data %>%
  leaflet() %>%
  addTiles() %>%
  addMarkers(lng = ~Longitude,
             lat = ~Latitude)

If we have numeric latitude and longitude columns in our data, addMarkers() will automatically use these. If your columns aren’t automatically detected, try using lat = ~as.numeric(Latitude). The error may be caused by numeric data stored as a character variable!

Geoplot options #

LinkedIn Learning 2.2, 2.3

  • We can display data if we hover over our points, or if we click on them
  • Hover data is given by the label argument of addMarkers()
  • Click data is given by the popup argument of addMarkets()
  • For each, we can display multiple variables or lines of text by adding arguments to paste0() or paste()
    • If we type this special string “
      ,” our following text begins on a new line
station_data %>%
  leaflet() %>%
  addTiles() %>%
  addMarkers(label = ~Station.name,
             popup = ~paste0("Altitude: ",
                             Elevation,
                             "<br/>",
                             "<br/>",
                             "Isn't Leaflet neat?"),
             lng = ~Longitude,
             lat = ~Latitude)

Click on the markers!

A note on Leaflet formatting #

Notice in the previous section, our arguments used a tilde (“~”) before we called on our column names. This even occured when we called them within a formula.

station_data %>%
  leaflet() %>%
  addTiles() %>%
  addMarkers(label = ~Station.name,
             popup = ~paste0("Altitude: ", Elevation),
             lng = ~Longitude,
             lat = ~Latitude)

We must do this, as this is the format Leaflet demands. It’s how Leaflet detects column names and converts them to lists of variables.

Creating circle marker geoplots #

LinkedIn Learning 2.2, 2.3

  • We simply use addCircleMarkers() instead of just addMarkers()
  • We also gain access to a number of additional options, some of which are shown here

Radius changes the size of circle plots according to a variable. Here we’d like the size to represent temperature.

station_data %>%
  leaflet() %>%
  addTiles() %>%
  addCircleMarkers(label = ~paste("The", Station.name, "circle!"),
                   radius = ~average.temp,
                   lng = ~Longitude,
                   lat = ~Latitude)

We can also change the colour of the circle markers according to a variable, although this requires some setup.

  • First we define a palette with the colorNumeric() function
  • The palette argument specifies the colours we move on a gradient between
  • The domain argument is the list of numeric values we map to the gradient
pal <- colorNumeric(
  palette = "YlOrRd",
  domain = station_data$average.temp)
  • We can then use this in the color argument of addCircleMarkers()
station_data %>%
  leaflet() %>%
  addTiles() %>%
  addCircleMarkers(label = ~paste("The", Station.name, "circle!"),
                   color = ~pal(average.temp),
                   lng = ~Longitude,
                   lat = ~Latitude)
  • R comes with several pre-built palettes such as “YlOrRd” and “Blues” to help colorNumeric()
  • Take note that using the colorRampPalette() function, it is possible to create custom palettes
  • In this example, the palette argument of colorNumeric() is defined using this special colorRampPalette() call
pal <- colorNumeric(
  palette = colorRampPalette(c("green", "purple"))(length(station_data$average.temp)),
  domain = station_data$average.temp)

Leaflet Choropleths #

LinkedIn Learning 2.4, 2.5

A choropleth is a map-based chart in which regions are shaded with colours to reflect some variable. Creating choropleths with Leaflet requires us to manipulate ‘shapefiles.’ These are files which contain information about points, lines, polygons (etc) necessary to visually depict shapes, such as countries of the world. Before we can create a choropleth, we must learn how to prepare these shape files

 

There are two types of shapefiles

  • ESRI shapefiles - the older standard for shapefiles
  • To use them we must have (at least) one of all of the below:
  • A .dbf file
  • A .shp file
  • A .shx file
  • GeoJson shapefiles - a newer type
  • To use them we only require one .json file

A good sources of global shapefiles are NaturalEarthData.com and Johan’s repository

Preparing shapefiles #

LinkedIn Learning 2.4

  • We require the library “sf
  • We call upon read_sf() to read an entire directory of shapefiles and save the result
  • For our example we will use an Australian shapefile released by the Australian Government
library("sf")
## Linking to GEOS 3.8.1, GDAL 3.1.4, PROJ 6.3.1
shapefile_map <- read_sf(dsn = "shapefiles")
# Note: for file path, do not include a '/' at the end
shapefile_map
## Simple feature collection with 8 features and 2 fields
## geometry type:  MULTIPOLYGON
## dimension:      XY
## bbox:           xmin: 112.9211 ymin: -43.74037 xmax: 159.1053 ymax: -9.142319
## CRS:            NA
## # A tibble: 8 x 3
##     STE COUNT                                                           geometry
##   <int> <dbl>                                                     <MULTIPOLYGON>
## 1     1 11619 (((151.0689 -33.82025, 151.0697 -33.81847, 151.0705 -33.82025, 15…
## 2     2  7889 (((146.3024 -39.15425, 146.3018 -39.15395, 146.3024 -39.1532, 146…
## 3     3  6373 (((153.3613 -27.63769, 153.3603 -27.63577, 153.3635 -27.63518, 15…
## 4     4  3152 (((137.5385 -34.01007, 137.5391 -34.01418, 137.5375 -34.01343, 13…
## 5     5  3481 (((115.2278 -33.59031, 115.2281 -33.59122, 115.2291 -33.59122, 11…
## 6     6  1089 (((146.5695 -41.17678, 146.5697 -41.17737, 146.5691 -41.17769, 14…
## 7     7   389 (((130.8684 -12.35691, 130.8692 -12.35513, 130.8699 -12.35691, 13…
## 8     8   492 (((149.2214 -35.34117, 149.2198 -35.34141, 149.2189 -35.34153, 14…
  • Note that our shapefile_map variable has a column called “geometry”
    • Each value in “geometry” is a list of lattitudes and longitudes
    • These geographical points map out a shape, hence our “shapefiles”
  • After reading the files in, we simply pipe them into the addPolygons() Leaflet function to view our map
shapefile_map %>%
  leaflet() %>%
  addPolygons()

Creating Leaflet choropleths and Legends #

LinkedIn Learning 2.5, 2.7

We have our shapes - we will now mutate our shape data so that they are named by state.

shapefile_map$State <- c("NSW", "VIC", "QLD", "SA", "WA", "TAS", "NT", "ACT")
  • Of course we want to colour our chart
  • We first create a palette

What colours do we use?

  • Color Brewer is a website with many palettes
  • We will use the one called “Set1”

How do we create our palette?

-Several functions exist

  • colorNumeric() for continuous numeric variable colouring
  • colorQuantile() for colouring by quantile of a numeric variable
  • colorBin() for colouring by bins of a numeric variable
  • colorFactor() for colouring by a categorical variable

Right now we have a map but no data. Let us colour by “State,” a categorical variable.

palette <- colorFactor("Set1", domain = shapefile_map$State)

shapefile_map %>%
  leaflet() %>%
  addTiles() %>%
  addPolygons(color = ~palette(State)) %>%
  addLegend(pal = palette, values = ~State, title = "State: ")
  • Note that color is an argument of addPolygons()
  • Note that the palette object is a function we are applying to the “State” column

Here we have introduced the Leaflet addLegend() function

  • The pal argument specifies the palette we use for the legend
  • The values argument specifies the variable our legend explains
  • The title argument titles the legend

Let us add some actual data to our choropleth. We will use the 2013-2014 subset of water usage data from the Australian Environmental-Economic Accounts (2016)

  • To see how this data was processed, see building_state_data.R
  • We can add extra data simply by merging with respect to the “State” variable
load("tidy_EnvAcc_data/water_data.rdata")
water_data
##   State DistributedReuseSupply(GL) Consumption(GL) Expenditure($m)
## 1   ACT                         49              53             288
## 2   NSW                       6211            7508            4192
## 3    NT                         59             167             157
## 4   QLD                       2579            4145            3877
## 5    SA                        380            1077            1162
## 6   TAS                         99             390             245
## 7   VIC                       3238            3988            4554
## 8    WA                        643            1317            1597
##   HouseholdPhysical(GL) HouseholdMonetary($m) HouseholdPrice($/KL)
## 1                    31                    93                 3.00
## 2                   565                  1589                 2.81
## 3                    37                    60                 1.62
## 4                   370                  1109                 3.00
## 5                   125                   531                 4.25
## 6                    38                   118                 3.11
## 7                   366                  1240                 3.39
## 8                   339                   557                 1.64
water_data_map <- shapefile_map %>%
  merge(water_data, by = "State")

We may now make a new palette and colour it. Let us do this by the average price of water.

  • While we’re at it, let’s display the average price by hovering our mouse over the state
  • Additionally let’s make it so that if we click, we can see per household water consumption and expenditure
palette <- colorNumeric("Blues", domain = water_data_map$`HouseholdPrice($/KL)`)

water_data_map %>%
  leaflet() %>%
  addTiles() %>%
  addPolygons(color = ~palette(water_data_map$`HouseholdPrice($/KL)`),
              label = ~paste("Price:",
                            water_data_map$`HouseholdPrice($/KL)`),
              popup = ~paste("Average household consumption (KL):",
                             water_data_map$`HouseholdPhysical(GL)`,
                             "<br/>",
                             "Household expenditure ($m):",
                             water_data_map$`HouseholdMonetary($m)`)) %>%
  addLegend(pal = palette,
            values = ~water_data_map$`HouseholdPrice($/KL)`,
            title = "Average price of water ($/KL): ")

Just like with addMarkers(), the label and popup arguments still work!

Geoline maps #

LinkedIn Learning 2.6

  • These are another great feature of Leaflet that requires some preparation
  • We can create maps of arcs across the surface of the earth using the gcIntermediate() function
  • We specify our “to” and from points as latitudes and longitudes
    • For our example we will use the ACORN-SAT stations and plot the distance between the most eastern and most western stations

To begin, we need the “geosphere” and “sp” packages. We also need two data.frames, each containing only a set of latitudes and longitudes. For our example, we filter out only the most eastern and most western coordinates.

library("geosphere")
library("sp")

load("tidy_ACORN-SAT_data/station_data.rdata")
east_to_west <- station_data %>%
  filter(year == 2000)

start_loc <- east_to_west %>%
  filter(Longitude == max(Longitude)) %>%
  select(Longitude, Latitude)

end_loc <- east_to_west %>%
  filter(Longitude == min(Longitude)) %>%
  select(Longitude, Latitude)
  • We then use gcIntermediate() to create our lines
    • This is useful, but isn’t attached to our original data
  • We thus pipe the result into SpatialLinesDataFrame() specifying our data to create one integrated object
  • Finally we pipe this into st_as_sf() to format the object as an sf
distance <- gcIntermediate(p1 = start_loc,
                           p2 = end_loc,
                           n = 50,
                           addStartEnd = TRUE,
                           sp = TRUE) %>%
  SpatialLinesDataFrame(data = east_to_west) %>%
  st_as_sf()
  • Now we can use addPolylines() specifying our object to plot the lines
  • In our example this is only one line, but multiple lines are just as achievable
east_to_west %>%
  leaflet() %>%
  addTiles() %>%
  addCircleMarkers(label = ~Station.name) %>%
  addPolylines(data = distance)