Credit: Rob J Hyndman and George Athanasopoulos
Full Text Using R: https://otexts.org/fpp2/
We list the following methods for general knowledge and leave it to the reader to investigate their uses
Future values = Average historical values
Future Values = Last actual values
Future Value = Last actual value from the same season
For example a forecast for the coming january would be equal to last january's value
A slight variation on the Naive adjusting for an increase/decrease over time. This adjustment would be indicated by the slope of the line between the some two points in the past. This incorporates the historical average rate of change into future values. $$ \begin{equation} \label{eq1} \begin{split} \hat{y}_{T+h|T} & = y_T + \frac{h}{T-1} \sum_{t=2}^{T} (y_t - y_{t-1}) \\ & = y_T + h (\frac{y_T - y1}{T-1}) \end{split} \end{equation} $$
Time series decomposition works by determining the time factors influencing the response. Some examples might include an increase in sales for a retail store during the months leading up to christmas.
Let t = time then the model options are $$ y_t = S_t + T_t + R_t \text{additive decomposition}$$ $$ y_t = S_t * T_t * R_t \text{multiplicative decomposition}$$ $$ log(y_t) = log(S_t) * log(T_t) * log(R_t) \text{multiplicative alternative}$$
where the capital letters represent the three components
Which one to choose?
Additive is better when the level of the time series causes little fluctuation in
import pandas as pd
import numpy as np
import matplotlib.pylab as plt
%matplotlib inline
from matplotlib.pylab import rcParams
import seaborn as sns
Trend is defined by the moving average. There are several different methods for computing the moving average.
A centered moving avg of order m, m odd, as defined by $$ \hat{T}_t = \frac{1}{m} \sum_{j=-k}^{k} y_{t+j} \text{ where } m = 2k + 1 $$ Which is simply the centered mean of the m periods nearest t.
rcParams['figure.figsize'] = 20, 6
data = pd.read_csv('./Data/AirPassengers.csv')
#convert month to date
data['Date'] = pd.to_datetime(data['Month'], format='%Y-%m')
#print(data.dtypes)
#print(data.head())
# do a quick line plot
plt.plot(data['Date'],data['#Passengers'], label='Orig' )
# Add a moving average trend - The higher the m the straighter the trend becomes
data['MA5'] = data['#Passengers'].rolling(min_periods=2,window=5, center=True).mean()
plt.plot(data['Date'], data['MA5'], label='CMA-5' )
The moving average of a moving average can be used to centre an even-order MA. Making it symmetric about t. It is also useful in extracting a trend-cycle from a seasonal cycle. What's most interesting about this method is that it effectively weights each period. So a 2x4,8,12... MA works well for data containing quarterly periods. A 2x12,24,etc would best apply to say monthly data. This also has the added advantage of placing a lower weight to values as they enter and leave the calculation window.
A 2m-MA is equivalent to a weighted moving average of order m+1 where all observations take the weight
1/m, except for the first and last terms which take weights (1/(2m)
Side-note: A seasonal cycle is a fixed time-driven cycle like the weather (but it doesn't have to be yearly). A Trend-cycle is anything outside that
Example:
Seasonal = a spike in purchases every 12 months
Trend = the economy has peaks and troughs, but the length of time b/w is not fixed.
plt.plot(data['Date'],data['#Passengers'], label='Orig' )
# Add a 2x4MA to get a centred 4MA
data['MA4'] = data['#Passengers'].rolling(min_periods=2,window=4, center=True).mean()
data['MA2x4'] = data['MA4'].rolling(min_periods=2,window=2, center=True).mean()
plt.plot(data['Date'], data['MA2x4'], label='MA2x4' )
data['MA8'] = data['#Passengers'].rolling(min_periods=2,window=8, center=True).mean()
data['MA2x8'] = data['MA8'].rolling(min_periods=2,window=2, center=True).mean()
plt.plot(data['Date'], data['MA2x8'], label='MA2x8' )
plt.legend()
The process is similar to the additive but we replace the subtraction with division
Trend adjusted Series becomes $y_t / \hat{T_t}$
Remainder becomes $\hat{R_t} = y_t / (\hat{T_t} * \hat{S_t})$
We could use an example, let's use the Stats_models
import statsmodels.api as sm
dta = data['#Passengers']
dta.index = pd.DatetimeIndex(data['Date'])
#dta.head()
# If you have missing values: .interpolate(inplace=True)
# model='additive'
res = sm.tsa.seasonal_decompose(dta,model='multiplicative')
#results = pd.DataFrame(data[['Date','#Passengers']],res.trend,res.seasonal,res.resid)
#results.head()
resplot = res.plot()
# As we can see below Statsmodels can effciently pluck out the needed components.
# Most likely better than we can code our own solutions
Classical decomposition is still used but should be avoided if it can be. It makes a lot of assumptions which may not play out in the real world. It also over-smooths the data, and is error prone. Suppose a strike had happened during the timeframe of our data, then we would see an outlier event that would throw things out of whack. There are far better/robust methods out as we shall soon see.
The X11 method is an iterative classical decomposition. It involves a classical decomposition with one, or more, added iterative/adjustment steps that try to eliminate the effect of outliers.
The process can be described as follows:
Here is a basic overview:
Let $O_t$ be the original time series
Step 1 Estimate the Trend $\hat{T_t}$ $$ \frac{O_t}{\hat{T_t}} = \frac{(T_t * S_t * I_t)}{\hat{T_t}} \approx T_t*I_t $$
Step 2 Seasonal Estimate 1
Apply a weighted 5-term moving average ($S_{3x3}$) to the series $S_t I_t$ for each month separately. Missing values at the end of the seasonal component can be replace with the values from the previous year.
Step 3 Adjusted Estimate 1 $$ \frac{O_t}{\hat{S_t}} = \frac{(T_t * S_t * I_t)}{\hat{S_t}} \approx T_t*I_t $$
Step 4 Estimate the trend 2
Repeat step 1 on the Seasonally adjusted date
Step 5 Seasonal - Final Estimate Repeat step 2 to get final seasonal component
Step 6 Adjusted - Final estimate
Repeat step 3 using $\hat{S_t}$ from step 5
Step 7 Trend - Final Estimate
Repeat step 4
Step 8 Irregular - Final and Only Estimate $$ \frac{(T_t * I_t)}{\hat{T_t}} \approx I_t $$
Seats, Seasonal Extraction in Arima Time Series, works with quarterly & monthly data.
We won't go into this method as it is a specific type of ARIMA which will be discussed later.
Seasonal and Trend using Loess. Where Loess is a method for estimating nonlinear relationships. STL is a filtering procedure for decomposing a time series into three components.
Links
STL has several advantages over the previous methods
It has a few disadvantages as well
This second point can be negated by taking the log of the data then back transforming the data. Another trick is to use the Box-Cox transformation with $0 < \lambda < 1$. When $\lambda = 0$ you get the multiplicative decomposition. When $\lambda = 1$ you get the additive.
Recall: 1-param Box-Cox Transformation is defined as $$ \begin{cases} \frac{y^\lambda_i - 1}{\lambda} \text{ if } \lambda \neq 0 \\ ln(y_i) \text{ if } \lambda = 0 \end{cases} $$ If you use the transformation then you'll of course need the inverse functions to get to the actual forecast.
Unfortunately python does not have a forecast package that has an stl decomposition. However, there is an easy to follow and free piece of code available at https://github.com/jrmontag/STLDecompose