3 November 2015

Outline

  • What is R?
  • Why use R as a GIS?
  • Reading spatial data
  • Reprojecting
  • Clipping
  • Spatial joins
  • Points in polygon
  • Choropleth maps
  • Cluster maps

What is R?

is a free software environment and language for statistical analysis and data visualisation. Its programming language is open source and many statisticians and data scientists write packages for the software. R usage is increasing exponentially and many major companies like Google and Facebook run complex statistical analyses with it.

Why use R as a GIS?

  • Open source: No cost
  • Performance: R is stable and blazingly fast
  • Reproducibility: code can be shared and the results repeated
  • Transparency: code can be checked forensically and any errors spotted
  • Automation: statistical analyses and data visualisations can be run and re-run with new and existing data sets
  • Support network:
  • Integrated with ArcGIS: ArcGIS Bridge is an R package which allows ArcGIS and R to dynamically access data
  • and you can produce some amazing maps…….

A quick demonstration of R's basic GIS functions

Spatial data packages

Reading spatial data: polygons

Read the London borough boundary shapefile downloaded from the London Datastore using the readOGR function from the rgdal package and create a spatial object called 'boroughs'.

boroughs <- readOGR(".", "London_Borough_Excluding_MHW", verbose = FALSE)

Inspect the attribute table

head(boroughs@data, 4)
##                   NAME  GSS_CODE  HECTARES NONLD_AREA ONS_INNER
## 0 Kingston upon Thames E09000021  3726.117      0.000         F
## 1              Croydon E09000008  8649.441      0.000         F
## 2              Bromley E09000006 15013.487      0.000         F
## 3             Hounslow E09000018  5658.541     60.755         F

and extract a particular column using the $ symbol, e.g. boroughs$NAME

Reading spatial data: points

Read in some publicly available police recorded crime data

df <- read.csv("crime_data.csv", header = T)
head(df, 4)
##       long      lat              category
## 1 0.137065 51.58367 Anti-social behaviour
## 2 0.140035 51.58911 Anti-social behaviour
## 3 0.140192 51.58231 Anti-social behaviour
## 4 0.134947 51.58806 Anti-social behaviour

Convert the dataframe to a SpatialPointsDataFrame called 'crimes' with latitude / longitude projection

coords <- SpatialPoints(df[,c("long","lat")])
crimes <- SpatialPointsDataFrame(coords, df)
proj4string(crimes) <- CRS("+init=epsg:4326")

Reprojecting

Transform the borough boundary from epsg:27700 (British National Grid) to the epsg:4326 (WGS84) coordinate reference system.

boroughs <- spTransform(boroughs, CRS("+init=epsg:4326"))

Check the projection

boroughs@proj4string
## CRS arguments:
##  +init=epsg:4326 +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84
## +towgs84=0,0,0

Simple plotting

Plot the London borough boundaries and the crime data

plot(crimes)
plot(boroughs, border = "blue", add = T)

Clipping

Clip the crimes to the borough boundaries

proj4string(crimes) <- proj4string(boroughs)
crimes <- crimes[boroughs, ]

Plot the London borough boundaries and the crime data

plot(crimes)
plot(boroughs, border = "blue", add = T)

Spatial joins

Join attributes from the borough boundary polygon layer to the crime data points layer.

borough_attributes <- over(crimes, boroughs[,c("NAME", "GSS_CODE")])
crimes$borough <- borough_attributes$NAME
crimes$census_code <- borough_attributes$GSS_CODE

Use the dplyr package to generate some summary statistics on Bicycle theft

crimes %>%
  as.data.frame() %>%
  filter(category == "Bicycle theft") %>%
  count(borough) %>%
  arrange(desc(n)) %>% 
  head(4)
## Source: local data frame [4 x 2]
## 
##         borough     n
##          (fctr) (int)
## 1   Westminster   143
## 2       Hackney   123
## 3 Tower Hamlets   113
## 4       Lambeth   108

Points in polygon

Subset the Bicycle theft offences and count the number of points within each London borough using the over() function

bicycle_theft <- crimes[crimes$category == "Bicycle theft",]
proj4string(bicycle_theft) <- proj4string(boroughs)
pointsinpolygon <- over(SpatialPolygons(boroughs@polygons), 
                        SpatialPoints(bicycle_theft), returnList = TRUE)
boroughs$bicycle_theft <- unlist(lapply(pointsinpolygon, length))
head(boroughs@data, 4)
##                   NAME  GSS_CODE  HECTARES NONLD_AREA ONS_INNER
## 0 Kingston upon Thames E09000021  3726.117      0.000         F
## 1              Croydon E09000008  8649.441      0.000         F
## 2              Bromley E09000006 15013.487      0.000         F
## 3             Hounslow E09000018  5658.541     60.755         F
##   bicycle_theft
## 0            21
## 1            12
## 2            19
## 3            60

Choropleth mapping: Preparation

Create thematic intervals

classes <- classIntervals(boroughs$bicycle_theft, n=5, style="jenks")

Fortify the borough shapefile for plotting in ggplot2 / ggmap and merge the attribute data using the left_join() function from the dplyr package.

boroughs.f <- fortify(boroughs, region="NAME")
boroughs.f <- left_join(boroughs.f, boroughs@data, by = c("id" = "NAME"))

Choropleth mapping: ggplot2

ggplot() +
  geom_polygon(data = boroughs.f, 
               aes(x = long, y = lat, group = group,
                   fill = bicycle_theft), color = "white", size = 0.2) +
  coord_map() +
  scale_fill_gradientn('', colours=brewer.pal(5,"RdPu"), breaks = classes$brks) +
  theme_nothing(legend = TRUE) +
  ggtitle(expression(atop("Police recorded Bicycle theft offences by borough", 
                          atop(italic("August 2015")))))

Choropleth mapping: ggmap

Retrieve the bounding box from the London boroughs using the bbox function from the sp package

bb <- as.vector(boroughs@bbox)
bb
## [1] -0.5103751 51.2867602  0.3340155 51.6918741

Request a Stamen Maps raster layer using the get_map function from the ggmap package

map <- get_map(location = bb, source = "stamen", maptype = "toner", crop = FALSE)

Choropleth mapping: ggmap

ggmap(map, extent = "device") +
  geom_polygon(data = boroughs.f, aes(x = long, y = lat, group = group,
        fill = bicycle_theft), color = "white", size = 0.2, alpha = 0.6) +
  coord_map() +
  scale_fill_gradientn('', colours=brewer.pal(5,"RdPu"), breaks = classes$brks) +
  theme_nothing(legend = TRUE) + 
  ggtitle(expression(atop("Police recorded Bicycle theft offences by borough", 
                          atop(italic("August 2015")))))

Choropleth mapping: leaflet

Create a popup showing the borough name and count of Bicycle theft offences

boroughs_popup <- paste0("<strong>Borough: </strong>",
                         boroughs$NAME,
                         "<br><strong>Offences (August 2015): </strong>",
                         boroughs$bicycle_theft)

Then add a colour palette for the map

colcode <- findColours(classes, c("#feebe2", "#fbb4b9", "#f768a1", 
                                  "#c51b8a", "#7a0177"))

Choropleth mapping: leaflet

leaflet() %>% addTiles() %>%
  addPolygons(data = boroughs,
              fillColor = colcode,
              fillOpacity = 0.6,
              color = "white",
              weight = 2,
              popup = boroughs_popup)

Cluster mapping: leaflet

leaflet(boroughs) %>% addProviderTiles("CartoDB.Positron") %>% 
      fitBounds(bb[1], bb[2], bb[3], bb[4]) %>% 
      addPolygons(color = "grey", weight = 2, fillOpacity = 0.3) %>% 
      addCircleMarkers(data = bicycle_theft, ~long, ~lat, 
                       fillColor = "red", radius = 5, fillOpacity = 0.6, stroke = FALSE, 
                       clusterOptions = markerClusterOptions(
                         zoomToBoundsOnClick = TRUE,
                         spiderfyOnMaxZoom = TRUE, maxClusterRadius = 50))

Writing shapefiles

Write the spatial object as a shapefile with the updated Bicycle theft counts

writeOGR(boroughs, ".", "updated_boroughs", driver="ESRI Shapefile")

Advanced spatial analysis in R

  • Spatial autocorrelation
  • Point pattern analysis
  • Geographically weighted regression
  • Kriging

Have a look at Bivand, Pebesma, & Rubio (2013) for further information.

Thank you!