The internet seems to be booming with blog posts on animated graphs, whether it’s for more serious purposes or not so much. I didn’t think anything more of it than just a gimmick or a cool way of spicing up your conference talk. However, I’m a total convert now and in this post I want to show a real value that such graph can add to your (absolutely serious!) exploratory analysis.


IMPORT OF METROPOLITAN POLICE DATA

As an example, I’ll use geospatial data about crime and policing in the UK, freely available here. As I live in London, quite naturally I chose data for Metropolitan Police region, starting from January 2016 to March 2017, with only Include crime data option ticked..

The data are downloaded in the form of list of folders, each containing data for any specified month. In order to smoothly find and append those files, I used dir() function (after moving the folders to the working directory first, bien sûr):

london_files <- dir(recursive = T, pattern = "*metropolitan-street.csv", full.names=TRUE)
london_files
##  [1] "./london_police_data/2016-01/2016-01-metropolitan-street.csv"
##  [2] "./london_police_data/2016-02/2016-02-metropolitan-street.csv"
##  [3] "./london_police_data/2016-03/2016-03-metropolitan-street.csv"
##  [4] "./london_police_data/2016-04/2016-04-metropolitan-street.csv"
##  [5] "./london_police_data/2016-05/2016-05-metropolitan-street.csv"
##  [6] "./london_police_data/2016-06/2016-06-metropolitan-street.csv"
##  [7] "./london_police_data/2016-07/2016-07-metropolitan-street.csv"
##  [8] "./london_police_data/2016-08/2016-08-metropolitan-street.csv"
##  [9] "./london_police_data/2016-09/2016-09-metropolitan-street.csv"
## [10] "./london_police_data/2016-10/2016-10-metropolitan-street.csv"
## [11] "./london_police_data/2016-11/2016-11-metropolitan-street.csv"
## [12] "./london_police_data/2016-12/2016-12-metropolitan-street.csv"
## [13] "./london_police_data/2017-01/2017-01-metropolitan-street.csv"
## [14] "./london_police_data/2017-02/2017-02-metropolitan-street.csv"
## [15] "./london_police_data/2017-03/2017-03-metropolitan-street.csv"

This function recognizes all the specified files (here: csv files ending with metropolitan-street string) in the main folder, as well as sub-folders, genius! As you can see, thanks to full.names = TRUE, the object will return not only files’ names, but also their paths.

Next, I only need to append identified files…

london_police_data <- do.call(rbind,lapply(london_files, read.csv))
str(london_police_data)
## 'data.frame':    1237778 obs. of  12 variables:
##  $ Crime.ID             : Factor w/ 905661 levels "","0002285d1ab33fde301c313d3654e5bf45ce80eb10a90153f655a625dba32c30",..: 36371 1 1 1 1 1 1 1347 41162 24388 ...
##  $ Month                : Factor w/ 15 levels "2016-01","2016-02",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Reported.by          : Factor w/ 1 level "Metropolitan Police Service": 1 1 1 1 1 1 1 1 1 1 ...
##  $ Falls.within         : Factor w/ 1 level "Metropolitan Police Service": 1 1 1 1 1 1 1 1 1 1 ...
##  $ Longitude            : num  -0.77 0.141 0.137 0.14 0.136 ...
##  $ Latitude             : num  51.8 51.6 51.6 51.6 51.6 ...
##  $ Location             : Factor w/ 35240 levels "No Location",..: 17629 14588 14103 8507 10600 8507 7381 7183 8507 14588 ...
##  $ LSOA.code            : Factor w/ 6226 levels "","E01000001",..: 4634 25 25 25 25 25 25 25 25 25 ...
##  $ LSOA.name            : Factor w/ 6226 levels "","Aylesbury Vale 021B",..: 2 3 3 3 3 3 3 3 3 3 ...
##  $ Crime.type           : Factor w/ 14 levels "Anti-social behaviour",..: 14 1 1 1 1 1 1 3 5 5 ...
##  $ Last.outcome.category: Factor w/ 24 levels "","Awaiting court outcome",..: 8 1 1 1 1 1 1 8 13 18 ...
##  $ Context              : logi  NA NA NA NA NA NA ...

… and we can now start!

CREATING A STATIC VIEW HEATMAP

Let’s have a look at crime types and their frequencies:

sort(table(london_police_data$Crime.type))
##
##        Possession of weapons                  Other crime
##                         5913                        13174
##                Bicycle theft                      Robbery
##                        22238                        29211
##                        Drugs        Theft from the person
##                        42629                        47064
##                 Public order                  Shoplifting
##                        56748                        59022
##    Criminal damage and arson                     Burglary
##                        77491                        87388
##                Vehicle crime                  Other theft
##                       114303                       133894
## Violence and sexual offences        Anti-social behaviour
##                       258061                       290642

It looks like Possession of weapons is, thankfully, the least common reported crime, so let’s explore where those crimes usually happen and if there’s any obvious seasonality. I’ll start with creating a separate dataframe:

library(dplyr)

weapon_map_data <- london_police_data %>%
  filter(Crime.type == "Possession of weapons") %>%
  select(Month, Longitude, Latitude, Crime.type)

And a quick peek into sample sizes…

table(weapon_map_data$Month)
##
## 2016-01 2016-02 2016-03 2016-04 2016-05 2016-06 2016-07 2016-08 2016-09
##     323     285     310     336     399     488     457     457     401
## 2016-10 2016-11 2016-12 2017-01 2017-02 2017-03
##     408     351     332     399     415     552

Next, I’ll create a plain map of London using ggmap package:

#install.packages("ggmap", type = "source")
#devtools::install_github("hadley/ggplot2")
library(ggmap)
library(ggplot2)
library(evaluate)

evaluate("london_map = get_map(location = 'London', maptype='toner',  zoom = 10)")
ggmap(london_map)

london_map

Not bad for two lines of code, ey!

(Note commented part with package installation: I had to install ggmap and ggplot2 packages this way, otherwise the maps presented here wouldn’t get generated)

Finally, here’s a static heat map of weapon possession crimes in London, between January 2016 and March 2017:

weapon_london_heat_map<- ggmap(london_map, extent = "device") +
  stat_density_2d(aes(x = Longitude, y = Latitude, fill = ..level.., alpha=1),
                  data=weapon_map_data, geom = "polygon") +
  scale_fill_gradient(low = "blue", high = "red") +
  scale_alpha(range = c(0.00, 0.5), guide = FALSE)

weapon_london_heat_map

weapon_static

Not bad at all! We can now identify the crime hotspots, but there’s no way we can infer anything about the crime seasonality. And here’s where the first serious use of animated graphs comes in!


CREATING ANIMATED SINGLE-VIEW HEAT MAP

For this purpose I use, now famous, gganimate package. If you ever thought that creating gif’s with changing plots is hard, you’d better start eyeballing the below code, because the only difference between the static and animated graph is frame = Month part added to graph’s aes(). Simples.

#devtools::install_github("dgrtwo/gganimate")
library(gganimate)

map_anime<- ggmap(london_map, extent = "device") +
  stat_density_2d(aes(x = Longitude, y = Latitude, frame = Month,
                      fill = ..level.., alpha=1),
                  data=weapon_map_data, geom = "polygon") +
  scale_fill_gradient(low = "blue", high = "red") +
  scale_alpha(range = c(0.00, 0.5), guide = FALSE)
gganimate(map_anime)

weapon_gif

From this animation alone (pretty much) you would know which of the following statements is true: i) weapon-carrying criminals like Easter and summer holidays, thus take time off from their criminal activity during these times and thus reducing the geographical range of such crimes, OR ii) during holiday periods the weapon-carrying criminals tend to ‘focus’ on more central areas, supposedly while keeping up with their criminal activity…?

CREATING ANIMATED MULTIPLE-VIEW HEAT MAP

Following the same logic, we can create a faceted-animated view of all crimes in London over 15 months. It goes like this:

# creating a new data.frame
all_map_data <- london_police_data %>%
  select(Month, Longitude, Latitude, Crime.type)

# animated all london crimes over time
all_london_heat_map<- ggmap(london_map, extent = "device") +
  stat_density_2d(aes(x = Longitude, y = Latitude, frame = Month,
                      fill = ..level.., alpha=1),
                  data=all_map_data, geom = "polygon") +
  scale_fill_gradient(low = "blue", high = "red") +
  scale_alpha(range = c(0.00, 0.5), guide = FALSE) +
  facet_wrap(~ Crime.type, nrow = 3)

gganimate(all_london_heat_map)

animated_all

So, there you go! At the first glance it may look a bit chaotic, but such visualization will quickly make you realise that some crimes always have a narrow geographical range ( Theft from the person or Other theft, for example), especially compared to some with universally wide range (e.g. Burglary or Criminal damage and arson). And this is the first step for generating new questions and hypotheses, the integral (and very desirable) part of any exploratory analysis!

So, what do you think? Are you converted yet? :)