Simple Exponential Smoothing

WFJ Example

In this example, we need load in the dataset of WFJ sales.

WFJ_Full <- read.csv("WFJ_Sales_Full.csv")
WFJ_Full.ts <- ts(WFJ_Full$WFJ.Sales,start = 1,frequency = 1)

The only parameter we need for SES is \(\aplha\). The parameter can be user specified, or generated by R. Here for example, we want to make SES with alpha = 0.2, 0.5, and 0.7.

alpha_set <- c(0.2, 0.5, 0.7)
alpha_set
## [1] 0.2 0.5 0.7

Then let’s create a new matrix to store SES forecasts.

WFJ.frc.SES <- matrix(NA, nrow = 62, ncol = 3)
colnames(WFJ.frc.SES) <- paste("alpha=", alpha_set)
head(WFJ.frc.SES)
##      alpha= 0.2 alpha= 0.5 alpha= 0.7
## [1,]         NA         NA         NA
## [2,]         NA         NA         NA
## [3,]         NA         NA         NA
## [4,]         NA         NA         NA
## [5,]         NA         NA         NA
## [6,]         NA         NA         NA

Use the iteration to calculate the predicted values

Let’s recall the formula from our lecture:

\[ F_{t+1}=L_{t+1}=(1-a) L_t+a y_t=L_t+a e_t \]

So, for each time, to calculate the prediction, all we need are last time period’s prediction error and smoothing parameter.

WFJ.frc.SES_iter <- WFJ.frc.SES
for(i in 2:62){
  if(i == 2) {
    predicted <- WFJ_Full.ts[1] 
    WFJ.frc.SES_iter[2,] <- rep(predicted,3)
  }else{
    for(k in 1:3){
      alpha <- alpha_set[k]
      error <- WFJ_Full.ts[(i-1)] - WFJ.frc.SES_iter[(i-1),k]
      predicted <- WFJ.frc.SES_iter[(i-1),k] + alpha * error
      WFJ.frc.SES_iter[i,k] <- predicted
    }
  }
}
head(WFJ.frc.SES_iter)
##      alpha= 0.2 alpha= 0.5 alpha= 0.7
## [1,]         NA         NA         NA
## [2,]   23056.13   23056.13   23056.13
## [3,]   23408.37   23936.72   24288.96
## [4,]   23586.60   24118.14   24296.37
## [5,]   23517.78   23680.30   23558.64
## [6,]   23386.68   23271.30   23071.20

use ets() function

ets() function is used to fit exponential smoothing state space model. We will discuss this kind of models later this semester.

library(forecast)
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
for (i in 2:62){
  k <- i  
  ts.ref <- WFJ_Full.ts[1:(k-1)]
  for (j in 1:3){ # we have 3 alphas
    fit.model <- ets(ts.ref,model="ANN",alpha=alpha_set[j])
    # in model part, the first letter A stands for our error term is additive, which means the random error is additive the prediction.
    # the second letter denotes trend. For here, we do not have trend in our model, so it is N: None
    # the third letter denotes season. For here, we do not have seasonal pattern in our model, so it's N.
    WFJ.frc.SES[i,j] <- forecast::forecast(fit.model,h=1)$mean
  }
}
head(WFJ.frc.SES)
##      alpha= 0.2 alpha= 0.5 alpha= 0.7
## [1,]         NA         NA         NA
## [2,]   23056.13   23056.13   23056.13
## [3,]   23408.37   23936.72   24288.96
## [4,]   23586.60   24118.14   24296.37
## [5,]   23517.78   23680.30   23558.64
## [6,]   23386.68   23271.30   23071.20

The results are slightly different from what we get through the manual calculation. It is because of the algorithm the function used, it did some approximation to boost the calculation.

Make time series plot

WFJ_SES_0.2.ts <- ts(WFJ.frc.SES[,1],start = 2,end = 62,frequency = 1)
WFJ_SES_0.5.ts <- ts(WFJ.frc.SES[,2],start = 2,end = 62,frequency = 1)
WFJ_SES_0.7.ts <- ts(WFJ.frc.SES[,3],start = 2,end = 62,frequency = 1)

autoplot(WFJ_Full.ts,ylab="Weekly Sales", main = "WFJ Sales & SES Prediction",xlab = "Time/Week", series = "Actual WFJ Sales") +
  autolayer(WFJ_SES_0.2.ts, col = 2, series = "SES(0.2)") +
  autolayer(WFJ_SES_0.5.ts, col = 3, series = "SES(0.5)") +
  autolayer(WFJ_SES_0.7.ts, col = 3, series = "SES(0.7)") 
## Warning: Removed 1 row containing missing values (`geom_line()`).
## Removed 1 row containing missing values (`geom_line()`).
## Removed 1 row containing missing values (`geom_line()`).

Compare the performance of each alpha

We can calculate the RMSE of the 3 models.

error_SES_0.2 <- WFJ_Full.ts - WFJ_SES_0.2.ts
error_SES_0.5 <- WFJ_Full.ts - WFJ_SES_0.5.ts
error_SES_0.7 <- WFJ_Full.ts - WFJ_SES_0.7.ts


RMSE_SES_0.2 <- sqrt(mean(error_SES_0.2^2, na.rm = T))
RMSE_SES_0.5 <- sqrt(mean(error_SES_0.5^2, na.rm = T))
RMSE_SES_0.7 <- sqrt(mean(error_SES_0.7^2, na.rm = T))

c(RMSE_SES_0.2, RMSE_SES_0.5, RMSE_SES_0.7)
## [1] 4871.759 4490.625 4424.289

Among 0.2, 0.5, 0.7, \(\alpha = 0.7\) is the best smoothing parameter.

Select Optimal Paramter by ets()

ets(WFJ_Full.ts,model="ANN")$par
##        alpha            l 
## 7.310805e-01 2.347550e+04

The optimal \(\alpha\) selected by ets() is 0.731.

Linear Exponential Smoothing

Read in data and basic visualization

When a time series has a long-term trend, we should consider using linear exponential smoothing model. Here we have the total revenues of Netflix. First of all, let’s draw a time series plot of it.

Netflix <- read.csv("Netflix.csv")
Netflix.ts <- ts(Netflix$Total.Revenues,start = c(2000,1), frequency = 4)
autoplot(Netflix.ts)

From the time series plot, obviously, there is an increasing trend. An LES model should be adopted.

Find optimal parameters

We can use ets() function to help us find optimal parameters.

# Find optimal alpha and beta
ets(Netflix.ts ,model="AAN")$par
##      alpha       beta          l          b 
##  0.9999000  0.4420412 -0.3568988  3.7055700
alpha_opt <- ets(Netflix.ts ,model="AAN")$par[1]

We can find the optimal parameters for the LES model are \(\alpha = 1, \beta = 0.44\).

We can also specify \(\alpha\) by ourselves. For example, we also set \(\alpha\) = 0.2, 0.5, or 0.8. To find the corresponding optimal \(\beta\), we can also use ets() function.

alpha <- c(0.2,0.5,0.8,alpha_opt)
k <- length(alpha)
# Find optimal betas.
beta <- vector("numeric",k)

for (i in 1:k){
  pars <- ets(Netflix.ts,model="AAN",alpha=alpha[i])$par
  beta[i] <- pars[which(names(pars)=="beta")]
}

Next is to create a matrix to store the predictions we are going to make. We set the column names as their parameters.

frc <- matrix(NA,nrow = 64,ncol = 4)
colnames(frc) <- paste("alpha=", alpha_set,"beta = ", round(beta,2))
head(frc)
##      alpha= 0.2 beta =  0.2 alpha= 0.5 beta =  0.5 alpha= 0.7 beta =  0.55
## [1,]                     NA                     NA                      NA
## [2,]                     NA                     NA                      NA
## [3,]                     NA                     NA                      NA
## [4,]                     NA                     NA                      NA
## [5,]                     NA                     NA                      NA
## [6,]                     NA                     NA                      NA
##      alpha= 0.2 beta =  0.44
## [1,]                      NA
## [2,]                      NA
## [3,]                      NA
## [4,]                      NA
## [5,]                      NA
## [6,]                      NA

Use ets() to make predictions

We have the alpha and the corresponding optimal beta, so we can build a linear exponential smoothing model by using function ets(). Please note here, we need to set the argument of model in ets() as “AAN”, which will make the function to build a LES model. For an LES model, we have the basic equation as F = L + T + E. T is standing for trend, and E is standing for random error.

n <- length(Netflix.ts)

# Produce forecasts
for (i in 3:64){
  fitsample <- Netflix.ts[1:(i -1)]
  fitsample <- ts(fitsample,frequency=frequency(Netflix.ts),start=start(Netflix.ts))
  for (j in 1:k){
    # First we fit ets with the fixed alpha value and model="AAN" - we will let it optimise the initial trend
    fit <- ets(fitsample,model="AAN",alpha=alpha[j],beta=beta[j])
    # Produce the 1-step ahead forecast
    frc[i,j] <- forecast(fit,h=1)$mean
  }
}

head(frc)
##      alpha= 0.2 beta =  0.2 alpha= 0.5 beta =  0.5 alpha= 0.7 beta =  0.55
## [1,]                     NA                     NA                      NA
## [2,]                     NA                     NA                      NA
## [3,]               10.18068               8.750063                8.362294
## [4,]               12.01510              11.180453               11.935743
## [5,]               14.17947              14.552785               15.862556
## [6,]               16.75927              18.698005               20.110058
##      alpha= 0.2 beta =  0.44
## [1,]                      NA
## [2,]                      NA
## [3,]                8.633334
## [4,]               12.351960
## [5,]               16.019719
## [6,]               20.143650

Compare performance

LES_1 <- ts(frc[,1],start = c(2000,1),frequency = 4)
LES_2 <- ts(frc[,2],start = c(2000,1),frequency = 4)
LES_3 <- ts(frc[,3],start = c(2000,1),frequency = 4)
LES_4 <- ts(frc[,4],start = c(2000,1),frequency = 4)

autoplot(Netflix.ts,series = "Actual", main = "Netflix Revenue", ylab = "Total Revenue", xlab = "Time")+
  autolayer(LES_1) +
  autolayer(LES_2) +
  autolayer(LES_3) +
  autolayer(LES_4,series = "Optimal one")
## Warning: Removed 2 rows containing missing values (`geom_line()`).
## Removed 2 rows containing missing values (`geom_line()`).
## Removed 2 rows containing missing values (`geom_line()`).
## Removed 2 rows containing missing values (`geom_line()`).

sqrt(mean((Netflix.ts - LES_1)^2, na.rm = T))
## [1] 42.317
sqrt(mean((Netflix.ts - LES_2)^2, na.rm = T))
## [1] 24.92471
sqrt(mean((Netflix.ts - LES_3)^2, na.rm = T))
## [1] 20.68453
sqrt(mean((Netflix.ts - LES_4)^2, na.rm = T))
## [1] 19.33488