Combining Time Series Components

A series is thought to be an aggregate or combination of these four components.

All series have a level and noise. The trend and seasonality components are optional.

It is helpful to think of the components as combining either additively or multiplicatively.

Additive Model

An additive model suggests that the components are added together as follows:

1

y(t) = Level + Trend + Seasonality + Noise

An additive model is linear where changes over time are consistently made by the same amount.

A linear trend is a straight line.

A linear seasonality has the same frequency (width of cycles) and amplitude (height of cycles).

Multiplicative Model

A multiplicative model suggests that the components are multiplied together as follows:

1

y(t) = Level * Trend * Seasonality * Noise

A multiplicative model is nonlinear, such as quadratic or exponential. Changes increase or decrease over time.

A nonlinear trend is a curved line.

A non-linear seasonality has an increasing or decreasing frequency and/or amplitude over time.

Decomposition as a Tool

This is a useful abstraction.

Decomposition is primarily used for time series analysis, and as an analysis tool it can be used to inform forecasting models on your problem.

It provides a structured way of thinking about a time series forecasting problem, both generally in terms of modeling complexity and specifically in terms of how to best capture each of these components in a given model.

Each of these components are something you may need to think about and address during data preparation, model selection, and model tuning. You may address it explicitly in terms of modeling the trend and subtracting it from your data, or implicitly by providing enough history for an algorithm to model a trend if it may exist.

You may or may not be able to cleanly or perfectly break down your specific time series as an additive or multiplicative model.

Real-world problems are messy and noisy. There may be additive and multiplicative components. There may be an increasing trend followed by a decreasing trend. There may be non-repeating cycles mixed in with the repeating seasonality components.

Nevertheless, these abstract models provide a simple framework that you can use to analyze your data and explore ways to think about and forecast your problem.

Automatic Time Series Decomposition

The statsmodels library provides an implementation of the naive, or classical, decomposition method in a function called seasonal_decompose(). It requires that you specify whether the model is additive or multiplicative.

Both will produce a result and you must be careful to be critical when interpreting the result. A review of a plot of the time series and some summary statistics can often be a good start to get an idea of whether your time series problem looks additive or multiplicative.

The seasonal_decompose() function returns a result object. The result object contains arrays to access four pieces of data from the decomposition.

For example, the snippet below shows how to decompose a series into trend, seasonal, and residual components assuming an additive model.

The result object provides access to the trend and seasonal series as arrays. It also provides access to the residuals, which are the time series after the trend, and seasonal components are removed. Finally, the original or observed data is also stored.

1

2

3

4

5

6

7

from statsmodels.tsa.seasonal import seasonal_decompose

series=...

result=seasonal_decompose(series,model='additive')

print(result.trend)

print(result.seasonal)

print(result.resid)

print(result.observed)

These four time series can be plotted directly from the result object by calling the plot() function. For example:

1

2

3

4

5

6

from statsmodels.tsa.seasonal import seasonal_decompose

from matplotlib import pyplot

series=...

result=seasonal_decompose(series,model='additive')

result.plot()

pyplot.show()

Let’s look at some examples.

Additive Decomposition

We can create a time series comprised of a linearly increasing trend from 1 to 99 and some random noise and decompose it as an additive model.

Because the time series was contrived and was provided as an array of numbers, we must specify the frequency of the observations (the freq=1 argument). If a Pandas Series object is provided, this argument is not required.

1

2

3

4

5

6

7

8

from random import randrange

from pandas import Series

from matplotlib import pyplot

from statsmodels.tsa.seasonal import seasonal_decompose

series=[i+randrange(10)foriinrange(1,100)]

result=seasonal_decompose(series,model='additive',freq=1)

result.plot()

pyplot.show()

Running the example creates the series, performs the decomposition, and plots the 4 resulting series.

We can see that the entire series was taken as the trend component and that there was no seasonality.

Additive Model Decomposition Plot

We can also see that the residual plot shows zero. This is a good example where the naive, or classical, decomposition was not able to separate the noise that we added from the linear trend.

The naive decomposition method is a simple one, and there are more advanced decompositions available, like Seasonal and Trend decomposition using Loess or STL decomposition.

Caution and healthy skepticism is needed when using automated decomposition methods.

Multiplicative Decomposition

We can contrive a quadratic time series as a square of the time step from 1 to 99, and then decompose it assuming a multiplicative model.

1

2

3

4

5

6

7

from pandas import Series

from matplotlib import pyplot

from statsmodels.tsa.seasonal import seasonal_decompose

series=[i**2.0foriinrange(1,100)]

result=seasonal_decompose(series,model='multiplicative',freq=1)

result.plot()

pyplot.show()

Running the example, we can see that, as in the additive case, the trend is easily extracted and wholly characterizes the time series.

Multiplicative Model Decomposition Plot

Exponential changes can be made linear by data transforms. In this case, a quadratic trend can be made linear by taking the square root. An exponential growth in seasonality may be made linear by taking the natural logarithm.

Again, it is important to treat decomposition as a potentially useful analysis tool, but to consider exploring the many different ways it could be applied for your problem, such as on data after it has been transformed or on residual model errors.

Let’s look at a real world dataset.

Airline Passengers Dataset

The Airline Passengers dataset describes the total number of airline passengers over a period of time.

The units are a count of the number of airline passengers in thousands. There are 144 monthly observations from 1949 to 1960.

Download the dataset to your current working directory with the filename “airline-passengers.csv“.

First, let’s graph the raw observations.

1

2

3

4

5

from pandas import Series

from matplotlib import pyplot

series=Series.from_csv('airline-passengers.csv',header=0)

series.plot()

pyplot.show()

Reviewing the line plot, it suggests that there may be a linear trend, but it is hard to be sure from eye-balling. There is also seasonality, but the amplitude (height) of the cycles appears to be increasing, suggesting that it is multiplicative.

Plot of the Airline Passengers Dataset

We will assume a multiplicative model.

The example below decomposes the airline passengers dataset as a multiplicative model.

1

2

3

4

5

6

7

from pandas import Series

from matplotlib import pyplot

from statsmodels.tsa.seasonal import seasonal_decompose

series=Series.from_csv('airline-passengers.csv',header=0)

result=seasonal_decompose(series,model='multiplicative')

result.plot()

pyplot.show()

Running the example plots the observed, trend, seasonal, and residual time series.

We can see that the trend and seasonality information extracted from the series does seem reasonable. The residuals are also interesting, showing periods of high variability in the early and later years of the series.

Multiplicative Decomposition of Airline Passenger Dataset

Further Reading

This section lists some resources for further reading on time series decomposition.

Hi Alvaro,
maybe a bit late but it hope it helps. IMHO you are getting this error as you are feeding to seasonal_decompose() a pandas Series. If so, be sure to have a datetime type in your index or it will crash. You can make a turnaoround of this behavior just by passing the Series values to a np.array() and specifying the frequency manually,

Hi Jason and Álvaro,
Thanks Jason for the detailed description step by step. Highly appreciated!
I got the same problem using notebook, Jupiter. Please let me know if you figure out the problem.
thanks!

I did the followings and although I still receive an VisibleDeprecationWarning (using a non-integer number instead of an integer will result in an error in the future return np.r_[[np.nan] * head, x, [np.nan] * tail]),
I got the plots for time series components.

– I removed the last line in the CSV file: “International airline passengers: monthly totals in thousands. Jan 49 ? Dec 60”

Hello. Everything works OK when I test this dataset, but when I try to run it using data with daily temperature (from your other lesson: http://machinelearningmastery.com/time-series-seasonality-with-python/), I get the error: “ValueError: You must specify a freq or x must be a pandas object with a timeseries index witha freq not set to None”. I don’t understand why some data are not concerned as pandas object.

Yes, I understand that. The problem is that I use exactly the same piece of code for both files (data are loaded as pandas series). I want to avoid specyfing the frequency explicitly, because I would like to adapt this code to my own data, whre this freqency is unknown. Are there any specific requirements as regards CSV structure? I noticed that the file from this lesson has values for every month, whereas the other file mentioned by me has values given daily but I don’t believe it may be a reason for errors.

Hello, your texts are very interesting and useful. But in this text, I have a question. The example with a one column of data in dataset works well, but what I need to do to use the same code column by column in a multiple column dataset? Thank you!

Is there a way to find those signals that are periodic? I have a bunch of timeseries data (timestamp, uuid). I want to find those uuids that are seasonal. Expected output must be a set of uuids and their respective timestamps.

To notch it up a bit, given a large set of datapoints, is there a way to find all seasonal data with different seasonality?

Hi, Jason, very clear and helpful article.
I noticed that in the Airline Passengers example, seasonal and reside data ,first several values and last several values are lost. Is this a flaw of the algorithm?

if it is , any suggestion to fix this? cause usually people want to detect the most recent abnormal data instead of the historical ones.

Agree. I dug into the source code and the trend part is calculated by convolution method, so probably it is caused by rolling average.

So in my case, I can’t call this function directly cause I want to detect abnormality from residual part but its last values are nan. No one care a abnormality happened three days ago. Have to build my own function to achieve the goal.

Just curious in what situation people care about the historical abnormality. If not, this function is really awkward…

I tried stl function provided by R, and it worked well, no missing values at the beginning or end. Generally summarize my experience here, hope it helps people who are interested:

there are three common ways to decompose time series components:

1. use seasonal_decompose method provided by statsmodels.
In this case, one problem as far as I know is the first and last values of trend and residual are nan. People who care the most recent abnormality should be careful about this.

2. use stl function provided by R.

3. like Jason said, you can also choose to build your own version of the statsmodels function so you have more control

Hi Jason,
Thank you for the post.
In my data, I have weekly and yearly (possibly) seasonality. I have totally 16 months of daily data. How can I get these two seasonalities from seasonal_decompose method? Or, should I use some other method?

This is very useful analysis however there is a catch. In order to analyze seasonal changes of stock you need to specify decomposition frequency to an entire year as seasons repeat every year not every day. So if you put freq=252 (252 trading days in one year) you should be able to extract seasonal effects.

Hi Jason, thank you for your post.
What would you suggest for multivariate time series?
I have a time series dataset with 9 dependent variables, and one binary label. The time series consist of minutely based TS for a period of 3 months.
Should we perform the decomposition on each variable separately or what?
Thank you