Smoothing methods are based on averaging the past observations of the series over multiple time periods in order to reduce the noise. Moving average and simple exponential smoothing are the most popular and flexible methods for forecasting time series that rely on smoothing.
Such methods “smooth” out the noise in a series in an attempt to uncover the patterns and forecast the future values of the series. Different smoothers differ by the number of values averaged, how the average is computed, how many times averaging is performed, etc.
The main distinction between the exponential smoothing and the moving average approaches are summarized below:
The moving average functions (i.e. the simple and weighted moving average) can be used as a forecasting model with a small tweak. Moving average models are the foundation for the exponential smoothing forecasting models.
The exponential smoothing involves averaging all series observations, as opposed to a subset of \(m\) observations by the moving average method. Furthermore, the advance exponential smoothing functions can handle series with a trend and seasonal components.
38.1 The Simple Moving Average
The simple moving average (\(SMA\)) function for smooth time series data can be utilized, with some simple steps, as a forecasting model. This method is recommended when the input series has no structural patterns, such as trend and seasonal components. In this case, it is reasonable to assume that the forecasted values are relatively close to the last observations of the series.
However, there are a few popular methods for removing trends (de-trending) and removing seasonality (de-seasonalizing) from a series, such as regression models, advanced exponential smoothing methods, and the operation of differencing. The moving average can then be used to forecast such de-trended and de-seasonalized series, and then the trend and seasonality can be added back to the forecast.
Example:
In the following example, we will create a customized \(SMA\) function and use it to forecast the monthly prices of the Robusta coffee prices in the next 12 months. The Robusta coffee prices (USD per kg) are an example of time series data that has no specific trend or seasonal patterns. Rather, this series has a cycle component, where the magnitude and length of the cycle keep changing from cycle to cycle.
The Coffee_Prices series is a mts object with 2 variables and 701 observations
Frequency: 12
Start time: 1960 1
End time: 2018 5
# Extract the monthly prices of the Robusta coffeerobusta <- Coffee_Prices[,1]# Review the seriests_plot(robusta,title ="The Robusta Coffee Monthly Prices",Ytitle ="Price in USD",Xtitle ="Year")
Next, we will create a basic\(SMA\) function using some of the functionality of the tidyr package:
library(tidyr)sma_forecast <-function(df, h, m, w =NULL){# Error handlingif(h >nrow(df)){stop("The length of the forecast horizon must be shorter than the length of the series")}if(m >nrow(df)){stop("The length of the rolling window must be shorter than the length of the series")}if(!is.null(w)){if(length(w) != m){stop("The weight argument is not aligned with the length of the rolling window") } elseif(sum(w) !=1){stop("The sum of the average weight is different than 1") }}# Setting the average weigthsif(is.null(w)){ w <-rep(1/m, m) }### Setting the data frame #### Changing the Date object column namenames(df)[1] <-"date"# Setting the training and testing partition# according to the forecast horizon df$type <-c(rep("train", nrow(df) - h), rep("test", h))# Spreading the table by the partition type df1 <- df %>%spread(key = type, value = y)# Create the target variable df1$yhat <- df1$train# Simple moving average functionfor(i in (nrow(df1) - h +1):nrow(df1)){ r <- (i-m):(i-1) df1$yhat[i] <-sum(df1$yhat[r] * w) }# dropping from the yhat variable the actual values# that were used for the rolling window df1$yhat <-ifelse(is.na(df1$test), NA, df1$yhat) df1$y <-ifelse(is.na(df1$test), df1$train, df1$test)return(df1)}
The function arguments are as follows:
df: The input series in a two-column data frame format, where the first column is a Date object and the second one is the actual values of the series.
h: The horizon of the forecast. For the purpose of the following example, the function set the last \(h\) observations as a testing set. This allows us to compare model performance.
m: The length of the rolling window.
w: The weights of the average, by default, using equal weights (or arithmetic average).
The sma_forecast function has the following components:
Error handling: Test and verify whether the input arguments of the function are valid. If one of the defined tests isn’t true, it will stop the function from running and trigger an error message.
Data preparation: This defines the data.frame object based on the window length and the forecast horizon.
Data calculation: Calculates the simple moving average and return the results.
Now, let’s utilize the sma_forecast function to demonstrate the performance of the\(SMA\) function. We will forecast the last 24 months of the Robusta series using a rolling window of 3, 6, 12, 24, and 36 months:
robusta_df <-ts_to_prophet(robusta)robusta_fc_m1 <-sma_forecast(robusta_df, h =24, m =1)robusta_fc_m6 <-sma_forecast(robusta_df, h =24, m =6)robusta_fc_m12 <-sma_forecast(robusta_df, h =24, m =12)robusta_fc_m24 <-sma_forecast(robusta_df, h =24, m =24)robusta_fc_m36 <-sma_forecast(robusta_df, h =24, m =36)
We will use the plotly package to plot the results of the different moving average functions:
library(plotly)plot_ly(data = robusta_df[650:nrow(robusta_df),], x =~ ds, y =~ y,type ="scatter", mode ="lines",name ="Actual") %>%add_lines(x = robusta_fc_m1$date, y = robusta_fc_m1$yhat,name ="SMA - 1", line =list(dash ="dash")) %>%add_lines(x = robusta_fc_m6$date, y = robusta_fc_m6$yhat,name ="SMA - 6", line =list(dash ="dash")) %>%add_lines(x = robusta_fc_m12$date, y = robusta_fc_m12$yhat,name ="SMA - 12", line =list(dash ="dash")) %>%add_lines(x = robusta_fc_m24$date, y = robusta_fc_m24$yhat,name ="SMA - 24", line =list(dash ="dash")) %>%add_lines(x = robusta_fc_m36$date, y = robusta_fc_m36$yhat,name ="SMA - 36", line =list(dash ="dash")) %>%layout(title ="Forecasting the Robusta Coffee Monthly Prices",xaxis =list(title =""),yaxis =list(title ="USD per Kg."))
The main observations from the preceding plot are as follows:
If the length of the rolling window is shorter:
The range of the forecast is fairly close to the most recent observations of the series.
The faster the forecast converges to some constant value.
If the window length is longer:
The longer it takes until the forecast converges to some constant value.
It can handle better shocks and outliers.
An \(SMA\) forecasting model with a rolling window of a length of 1 is equivalent to the Naïve forecasting model.
Note that While the \(SMA\) function is fairly simple to use and cheap on compute power, it has some limitations:
The forecasting power of the \(SMA\) function is limited to a short horizon and may have poor performance in the long run.
This method is limited for time series data, with no trend or seasonal patterns. This mainly effects the arithmetic average that smooths the seasonal pattern and becomes flat in the long run.
38.2 Weighted Moving Average
The weighted moving average (WMA) is an extended version of the \(SMA\) function, and it is based on the use of the weighted average (as opposed to arithmetic average).
The main advantage of the \(WMA\) function, with respect to the \(SMA\) function, is that it allows you to distribute the weight of the lags on the rolling window. This can be useful when the series has a high correlation with some of its lags. The use of the \(WMA\) provides more flexibility as it can handle series with a seasonal pattern.
Example:
In the following example, we will use the sma_forecast function we created previously to forecast the last 24 months of the USgas dataset. In this case, we will utilize the w argument to set the average weight and therefore transform the function from \(SMA\) to \(WMA\). Like we did previously, we will first transform the series into data.frame format with the ts_to_prophet function:
data(USgas)USgas_df <-ts_to_prophet(USgas)
For this example, we will use the following two strategies:
The \(WMA\) model for applying all the weight on the seasonal lag (lag 12):
Let’s utilize the plotly package to plot the output of both \(WMA\) models:
plot_ly(data = USgas_df[190:nrow(USgas_df),],x =~ ds,y =~ y,type ="scatter",mode ="lines",name ="Actual") %>%add_lines(x = USgas_fc_m12a$date,y = USgas_fc_m12a$yhat,name ="WMA - Seasonal Lag",line =list(dash ="dash")) %>%add_lines(x = USgas_fc_m12b$date,y = USgas_fc_m12b$yhat,name ="WMA - 12 (0.2/0.8)",line =list(dash ="dash")) %>%layout(title ="Forecasting the Monthly Consumption of Natural Gas in the US",xaxis =list(title =""),yaxis =list(title ="Billion Cubic Feet"))
As you can see in the preceding plot, both models captured the seasonal oscillation of the series to some extent. Setting the full weight on the seasonal lag is equivalent to the seasonal Naïve model. This strategy could be useful for a series with a dominant seasonal pattern, such as \(USgas\).
In the second example, we weighted the average between the most recent lag and the seasonal lag. It would make sense to distribute the weights between the different lags when the series has a high correlation with those lags.
While the \(WMA\) can capture the seasonal component of a series, it cannot capture the series trend (due to the average effect). Therefore, this method will start to lose its effectiveness once the forecast horizon crosses the length of the series frequency (for example, more than a year for monthly series).
38.3 Removing Trend and Season (Differencing)
A simple and popular method for removing a trend and/or a seasonal pattern from a series is by the operation of differencing.
Differencing means taking the difference between two values:
A lag-1 difference (also called first difference) means taking the difference between every two consecutive values in the series (\(y_t − y_{t−1}\)). Lag-1 differencing is useful for removing a trend.
Differencing at lag-k means subtracting the value from \(k\) periods back (\(y_t − y_{t−k}\)). For example, for a daily series, lag-7 differencing means subtracting from each value (\(y_t\)) the value on the same day in the previous week (\(y_{t−7}\)).
38.3.1 Removing trend (de-trending)
Lag-1 differencing is useful for removing a trend. For quadratic and exponential trends, often another round of lag-1 differencing must be applied in order to remove the trend. This means taking lag-1 differences of the differenced series.
One advantage of differencing over other methods (e.g., a regression with a trend) is that differencing does not assume that the trend is global.
38.3.2 Removing seasonality (de-seasonalizing)
For removing a seasonal pattern with \(M\) seasons, we difference at \(lag M\). For example, to remove a day-of-week pattern in daily data, we can take lag- 7 differences. With a lag-12 differenced series the monthly pattern will no longer appears in a differenced series.
38.3.3 Removing trend and seasonality
When both trend and seasonality exist, we can apply differencing twice to the series in order to de-trend and deseasonalize it. For example, we can first apply differencing at lag-12, and then applying a lag-1 difference to the differenced series. The result is a series with no trend and no monthly seasonality in a monthly data series.