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))

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)

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)

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)

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 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.
