Skip to main content

Section 6.4 Time series analysis

A key way to ensure we are analysing our time data appropriately is to deal with time series data and treat them accordingly. Time series analysis looks at trends in crime or incidents. A crime or incident trend is a broad direction or pattern that specific types or general crime and/or incidents are following. Three types of trend can be identified:
  • overall trend – highlights if the problem is getting worse, better or staying the same over a period of time
  • seasonal, monthly, weekly or daily cycles of offences – identified by comparing previous time periods with the same period being analysed
  • random fluctuations – caused by a large number of minor influences, or a one-off event, and can include displacement of crime from neighbouring areas due to partnership activity or crime initiatives.
Decomposing these trends is an important part of what time series analysis is all about. We will see some examples.

Subsection 6.4.1 Plotting time series data

The data we will work with now is called femicidos.csv in the companion data (see Preamble to this book if not sure where to find this), and is a collection of intimate partner femicides from Spain.
femicidios <- read_csv("data/femicidos.csv")
This dataframe has only two columns, femicidos, which is a monthly observation of the number of intimate partner femicides per month, starting in January 2003, and month, which is the date for each observation. So, each row represents a monthly count of these crimes. We could do something like plot the number of crimes over each month using ggplot2.
library(ggplot2)

ggplot(femicidios, aes(x = month, y = femicidios, group = 1)) +
  geom_point() +
  geom_line() +
  theme_minimal() +
  ggtitle(label = "Number of femicides per month", subtitle = "In Spain") +
  theme(axis.text.x = element_text(hjust = 1, angle = 45))
A line plot with the title ’Number of femicides per month’, and subtitle ’In Spain’, over a grey grid. The vertical axis, labeled ’femicidios’, ranges from around one to eleven, and the horizontal axis, labeled ’month’, ranges from around 2003 to 2017. The graph is very noisy, with points plotted in all ranges in most years, and no patterns being immediately noticeable.
Figure 6.4.1. Plot the number of femicides per month
This visualisation gives us an insight into how the count of crimes varies between observations, but it subsumes in itself the three elements mentioned above (the overall trend, seasonal fluctuation, and random noise). This makes it difficult to isolate and discuss any one of these, and to answer the question about whether crime is going up or down, or whether there are any seasonal fluctuations present. In order to achieve this, we want to decompose the data into these components.
To look into decomposing into these components, we can use the functionality within R to deal with time series data. To take advantage of many of these, we will need our dataframe to be stored as a time series object. This enables us to apply R’s many functions for analysing time series data. To store the data in a time series object, we use the ts() function. Inside this function, we pass only the column which contains the number of crimes for each month (we filter with the select() function from dplyr package).
fem_timeseries <- ts(femicidios %>% dplyr::select(femicidios))
We have taken our dataframe of observed values in our time series (monthly observations of crime counts in this case) and transformed it into a matrix with class of ts — essentially representing our data as having been sampled at set intervals (in this case, every month). Once we have the result stored in our fem_timeseries object, we can auto print to see some details:
fem_timeseries
We can see that each observation point has been numbered in order, and we have a value for each observation (the number of femicides recorded in each month). Sometimes, the time series dataset that you have may have been collected at regular intervals that were less than one year, for example, monthly or quarterly. In this case, you can specify the number of times that data was collected per year by using the frequency parameter in the ts() function. For monthly time series data, you set frequency = 12, while for quarterly time series data, you set frequency = 4. You can also specify the first year that the data was collected, and the first interval in that year by using the start parameter in the ts() function. So, in our case, we would do as follows:
# transform into time series
fem_timeseries <- ts(femicidios %>%
                       select(femicidios), # specify dataframe selecting column
                     frequency=12,  # specify monthly frequency
                     start=c(2003,1)) # specify start time (January 2003)
Now that we have created this ts object, we can use the ts specific functions in order to extract meaning and insight. For example, going back to plotting our data so that we can see what sort of trends might be going on with crime, we can make use of the plot.ts() function, the basic plotting method for objects that are of class ts.
plot.ts(fem_timeseries)
The plot from the previous figure, now without titles and grid, is framed by a rectangle. The horizontal axis has gained the new label ’Time’, and has more years marked than before, starting at a notch for 2004 and every two years after, until 2016.
Figure 6.4.2. Basic plot of the ts object
This plot should look similar to the one we created using ggplot2 above; however, our observations are now treated as a continuous variable, labeled "Time". We can of course also use ggplot2 to plot a time series like the one we just did but here we would need a variable encoding the date (and preferably a full date, not just month and year as here). As you can see, it is very noisy. Fortunately, the annual count for intimate partner femicides is low in Spain. There seems to be some seasonality too. But what more can we do with plotting time series objects?
A seasonal time series consists of a trend component, a seasonal component and an irregular component. Decomposing the time series means separating the time series into these three components; that is, using statistics to estimate these three components. To estimate the trend component and seasonal component of a seasonal time series that can be described using an additive model, we can use the decompose() function in R. This function estimates the trend, seasonal, and irregular components of a time series using moving averages. It deals with additive or multiplicative seasonal components (the default is additive). The function decompose() returns a list object as its result, where the estimates of the seasonal component, trend component and irregular component are stored in named elements of that list objects, called "seasonal", "trend", and "random", respectively. Let’s now decompose this time series to estimate the trend, seasonal and irregular components.
fem_timeseriescomponents <- decompose(fem_timeseries)
The estimated values of the seasonal, trend and irregular components are now stored in variables fem_timeseriescomponents$seasonal, fem_timeseriescomponents$trend and fem_timeseriescomponents$random. For example, we can print out the estimated values of the seasonal component by typing:
fem_timeseriescomponents$seasonal
The estimated seasonal factors are given for the months from January to December and are the same for each year. The largest seasonal factor is for July (about 0.70), and the lowest is for February (about -0.76), indicating that there seems to be a peak in femicides in July and a trough in femicides in February each year. We can plot the estimated trend, seasonal, and irregular components of the time series by using the plot() function, for example:
plot(fem_timeseriescomponents)
A plot which is actually four plots tiled vertically, is labeled ’Decomposition of additive time series’. Each of the four plots has its own vertical axis, labeled ’observed’, ’trend’, ’seasonal’, and ’random’. The horizontal axis is as in the previous figure, ’Time’ ranging from 2004 to 2016. The ’observed’ plot is a vertically squished copy of the previous plot. The ’trend’ plot, ranging from three point five to six point five, is a relatively noise-free line. This plot stays above five and below six point five from 2004 to 2011, then gradually drops to three point five in 2015, before a small peak over five and dropping back down in 2016. The ’seasonal’ plot is a yearly periodic plot, ranging from under negative point five to over point five, with each year having a peak in the middle. The ’random’ plot ranges from negative four to positive four, and has high noise.
Figure 6.4.3. Decomposed time series of femicides in Spain
Once we remove the noise and the seasonal components, it becomes easier to see the estimated trend. Notice that while random and seasonal components still look messy, their scales are different and centred around zero.
We can adapt this code to decompose and estimate the trends for the aggravated assault data for NYC that we have used earlier in the chapter.
# select relevant column (assaults)
agassault_ny_d2 <- dplyr::select(agassault_ny_d, assaults)

#use ts() to transform to time series object
ny_timeseries <- ts(agassault_ny_d2, frequency=365, start=c(2014,1,1))

# decompose time series
ny_timeseriescomponents <- decompose(ny_timeseries)

#plot results
plot(ny_timeseriescomponents)
Another four plots tiled vertically, with the title ’Decomposition of additive time series’. The four plots’ vertical axes are once again labeled ’observed’, ’trend’, ’seasonal’, and ’random’. Of note is the ’trend’ plot, which seems to stay low until 2015, then climb throughout the year, and plateau at a higher value until 2017, when it drops again. The ’seasonal’ yearly periodic plot peaks in the middle of each year once again.
Figure 6.4.4. Decomposed time series of aggravated assaults in NYC
We can also use ggplot2 for these purposes. In particular, we can use the ggseas extension which allows for seasonal decomposition within ggplot (see [Ellis (2018)] for details). First, we can use the tsdf() function from the ggseas package. This turns the ts object we just created into a dataframe and then plot the series.
library(ggseas)

ny_df <- tsdf(ny_timeseries)
Then we can use the ggsdc() function in order to create a four-facetted plot of seasonal decomposition showing observed, trend, seasonal and random components.
ggsdc(ny_df, aes(x = x, y = y),  method = "decompose") +
   geom_line()
The previous four vertically tiled plots, now spaced out more, each with a grey grid background, a title of its own which was previously the vertical axis legend, and easier to read vertical axis numbers.
Figure 6.4.5. Decomposed time series of aggravated assaults in NYC using ggseas
The resulting graph similarly presents the components: the observed data, the trend, the seasonal component and the random fluctuation.
We have now covered a quick overview of ways of making sense of temporal data. Of course there is a lot more out there, and we urge interested readers to make use of the recommended reading section in this chapter to explore further the topic of temporal data analysis. But now let’s return to the spatial focus of our book.