Rlibrary(tidyverse)
library(sf)
library(raster)
library(spData)
library(spDataLarge)
library(tmap) # for static and interactive maps
library(leaflet) # for interactive maps
library(mapview) # for interactive maps
library(ggplot2) # tidyverse vis package
library(shiny) # for web applications
Amateur-looking maps can undermine your audience’s ability to understand important information and weaken the presentation of a professional data investigation.
tmap basics‘grammar of graphics’
# Add fill layer to nz shape
tm_shape(nz) +
tm_fill()
# Add border layer to nz shape
tm_shape(nz) +
tm_borders()
# Add fill and border layers to nz shape
tm_shape(nz) +
tm_fill() +
tm_borders()
+, followed by tm_*()fill, borders (demonstrated above), bubbles, text and raster (see tmap-element for a full list)tmap, we are able to store objects representing mapsmap_nz = tm_shape(nz) + tm_polygons()
class(map_nz)
#> [1] "tmap"
map_nz can be plotted later:
map_nz in the console, which is equivalent to print(map_nz).map_nz1 = map_nz +
tm_shape(nz_elev) + tm_raster(alpha = 0.7) # alpha makes the layer semi-transparent
+ tm_shape(new_obj).new_obj represents a new spatial object to be plotted on top of preceding layers.nz_water = st_union(nz) %>% st_buffer(22200) %>%
st_cast(to = "LINESTRING")
map_nz2 = map_nz1 +
tm_shape(nz_water) + tm_lines()
map_nz3 = map_nz2 +
tm_shape(nz_height) + tm_dots()
tmap_arrange().tmap_arrange(map_nz1, map_nz2, map_nz3)
ggplot2, tmap accepts aesthetic arguments that are either variable fields (based on column names) or constant valuescol, alpha, lwd, and lty arguments, respectivelyma1 = tm_shape(nz) + tm_fill(col = "red")
ma2 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3)
ma3 = tm_shape(nz) + tm_borders(col = "blue")
ma4 = tm_shape(nz) + tm_borders(lwd = 3)
ma5 = tm_shape(nz) + tm_borders(lty = 2)
ma6 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3) +
tm_borders(col = "blue", lwd = 3, lty = 2)
tmap_arrange(ma1, ma2, ma3, ma4, ma5, ma6)
R code below, tmap aesthetic arguments will not accept a numeric vectorplot(st_geometry(nz), col = nz$Land_area) # works
tm_shape(nz) + tm_fill(col = nz$Land_area) # fails
#> Error: Fill argument neither colors nor valid variable name(s)
col (and other aesthetics that can vary such as lwd for line layers and size for point layers) requires a character string naming an attribute associated with the geometry to be plottedtm_shape(nz) + tm_fill(col = "Land_area")
title sets the title of the associated legendlegend_title = expression("Area (km"^2*")") # expression() helps create superscript text
map_nza = tm_shape(nz) +
tm_fill(col = "Land_area", title = legend_title) + tm_borders()
breaks allows you to manually set the breaks.n sets the number of bins into which numeric variables are categorized.palette defines the color scheme, for example BuGn.tm_shape(nz) + tm_polygons(col = "Median_income")
breaks = c(0, 3, 4, 5) * 10000
tm_shape(nz) + tm_polygons(col = "Median_income", breaks = breaks)
tm_shape(nz) + tm_polygons(col = "Median_income", n = 10)
tm_shape(nz) + tm_polygons(col = "Median_income", palette = "BuGn")
tmap also allows users to specify algorithms to automatically create breaks with the style argument
style = pretty, the default setting, rounds breaks into whole numbers where possible and spaces them evenly.style = equal divides input values into bins of equal range, and is appropriate for variables with a uniform distribution (not recommended for variables with a skewed distribution as the resulting map may end-up having little color diversity).style = quantile ensures the same number of observations fall into each category (with the potential down side that bin ranges can vary widely).style = jenks identifies groups of similar values in the data and maximizes the differences between categories.style = cont (and order) present a large number of colors over continuous color field, and are particularly suited for continuous rasters (order can help visualize skewed distributions).style = cat was designed to represent categorical values and assures that each category receives a unique color.palette defines the color ranges associated with the bins and determined by the breaks, n, and style argumentsadd a - as prefix to reverse the palette order
Sequential palettes can be single (Blues go from light to dark blue, for example) or multi-color/hue (YlOrBr is gradient from light yellow to brown via orange, for example)
tm_shape(nz) + tm_polygons("Population", palette = "Blues")
tm_shape(nz) + tm_polygons("Population", palette = "YlOrBr")
midpoint argumentTwo important principles
map_nz +
tm_compass(type = "8star", position = c("left", "top")) + # north arrows
tm_scale_bar(breaks = c(0, 100, 200), size = 1) # scale bars
# see args(tm_layout) or ?tm_layout
map_nz + tm_layout(title = "New Zealand")
map_nz + tm_layout(scale = 5)
map_nz + tm_layout(bg.color = "lightblue")
map_nz + tm_layout(frame = FALSE)
frame.lwd) and an option to allow double lines (frame.double.line).outer.margin and inner.margin.fontface and fontfamily.legend.show (whether or not to show the legend) legend.only (omit the map) and legend.outside (should the legend go outside the map?), as well as multiple choice settings such as legend.position.aes.color), map attributes such as the frame (attr.color).Color settings controlling sepia.intensity (how yellowy the map looks) and saturation (a color-grayscale).
Beyond the low-level control over layouts and colors, tmap also offers high-level styles, using the tm_style() function
map_nza + tm_style("bw")
map_nza + tm_style("classic")
map_nza + tm_style("cobalt")
map_nza + tm_style("col_blind")
urb_1970_2030 = urban_agglomerations %>%
filter(year %in% c(1970, 1990, 2010, 2030))
tm_shape(world) + tm_polygons() +
tm_shape(urb_1970_2030) + tm_symbols(col = "black", border.col = "white",
size = "population_millions") +
tm_facets(by = "year", nrow = 2, free.coords = FALSE)
by argument which varies depending on a variable (year in this case).nrow/ncol setting specifying the number of rows and columns that facets should be arranged into.free.coords-parameter specifying if each map has its own bounding box.It could serve many different purposes, including providing a context or bringing some non-contiguous regions closer to ease their comparison.
In the example below, the first step is to define the area of interest, which can be done by creating a new spatial object, nz_region.
nz_region =
st_bbox(c(xmin = 1340000, xmax = 1450000,
ymin = 5130000, ymax = 5210000),
crs = st_crs(nz_height)) %>%
st_as_sfc()
nz_height_map =
tm_shape(nz_elev, bbox = nz_region) +
tm_raster(style = "cont", palette = "YlGn", legend.show = TRUE) +
tm_shape(nz_height) +
tm_symbols(shape = 2, col = "red", size = 1) +
tm_scale_bar(position = c("left", "bottom"))
nz_map =
tm_shape(nz) +
tm_polygons() +
tm_shape(nz_height) +
tm_symbols(shape = 2, col = "red", size = 0.1) +
tm_shape(nz_region) +
tm_borders(lwd = 3)
viewport() from the grid package
x and y) and a size (width and height) of the inset map.library(grid)
nz_height_map
print(nz_map, vp = viewport(0.8, 0.27, width = 0.5, height = 0.5))
# first create a map for the contiguous US with an appropriate projection
us_states_map = tm_shape(us_states, projection = 2163) + tm_polygons() +
tm_layout(frame = FALSE)
# create two states on separate maps.
hawaii_map = tm_shape(hawaii) + tm_polygons() +
tm_layout(title = "Hawaii", frame = FALSE, bg.color = NA,
title.position = c("LEFT", "BOTTOM"))
alaska_map = tm_shape(alaska) + tm_polygons() +
tm_layout(title = "Alaska", frame = FALSE, bg.color = NA)
# now, combine and arrange these three maps
us_states_map
print(hawaii_map, vp = grid::viewport(0.35, 0.1, width = 0.2, height = 0.1))
print(alaska_map, vp = grid::viewport(0.15, 0.15, width = 0.3, height = 0.3))
Skip
tmap_mode("view")
map_nz
map_nz + tm_basemap(server = "OpenTopoMap")
tmap’s view mode also works with faceted plots.world_coffee = left_join(world, coffee_data, by = "name_long")
facets = c("coffee_production_2016", "coffee_production_2017")
tm_shape(world_coffee) +
tm_polygons(facets) +
tm_facets(nrow = 1, sync = TRUE)
# If you are not proficient with `tmap`, the quickest way to create interactive maps may be with `mapview`.
mapview::mapview(nz)
mapview has a concise syntax yet is powerful.trails %>%
st_transform(st_crs(franconia)) %>%
st_intersection(franconia[franconia$district == "Oberfranken", ]) %>%
st_collection_extract("LINE") %>%
mapview(color = "red", lwd = 3, layer.name = "trails") +
mapview(franconia, zcol = "district", burst = TRUE) + # 'burst' datasets into multiple layers
breweries # addition of multiple layers with + followed by the name of a geographic object
Other options:
leaflet
- the most mature and widely used interactive mapping package in R
- provides a relatively low-level interface to the Leaflet JavaScript library
- many of its arguments can be understood by reading the documentation of the original JavaScript library (check Leaflet API reference)
pal = colorNumeric("RdYlBu", domain = cycle_hire$nbikes)
leaflet(data = cycle_hire) %>%
addProviderTiles(providers$OpenStreetMap.BlackAndWhite) %>%
addCircles(col = ~pal(nbikes), opacity = 0.9) %>%
addPolygons(data = lnd, fill = FALSE) %>%
addLegend(pal = pal, values = ~nbikes) %>%
setView(lng = -0.1, 51.5, zoom = 12) %>%
addMiniMap()
A few limitaions of interactive mapping approaches above
# this code chunk is not run here. Try in your R session.
library(shiny) # for shiny apps
library(leaflet) # renderLeaflet function
library(spData) # loads the world dataset
ui = fluidPage(
sliderInput(inputId = "life", "Life expectancy", 49, 84, value = 80),
leafletOutput(outputId = "map")
)
server = function(input, output) {
output$map = renderLeaflet({
leaflet() %>% addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
addPolygons(data = world[world$lifeExp < input$life, ])})
}
shinyApp(ui, server)
The user interface (ui) of lifeApp is created by fluidPage(). This contains input and output ‘widgets’ — in this case, a sliderInput() (many other *Input() functions are available) and a leafletOutput(). These are arranged row-wise by default, explaining why the slider interface is placed directly above the map (see ?column for adding content column-wise).
The server side (server) is a function with input and output arguments. output is a list of objects containing elements generated by render*() function — renderLeaflet() which in this example generates output$map. Input elements such as input$life referred to in the server must relate to elements that exist in the ui — defined by inputId = "life" in the code above.
The function shinyApp() combines both the ui and server elements and serves the results interactively via a new R process. When you move the slider in the map, you are actually causing R code to re-run, although this is hidden from view in the user interface.
Resources:
?shiny in your R sessionSkip