At its core, a single exponential time series can predict values without trend (increase or decrease over longer periods of time) or seasonality (repetition of patterns at regular intervals, i.e. yearly, monthly, etc.). The double exponential time series adds trend to the model while the triple exponential time series adds trend and seasonality. We're also adding the model that accounts for seasonality without accounting for trend. We're not sure what this model is called, so we'll call it the Reverse Double Exponential Model for now. If you have any idea what it's called or if it's invalid for some reason, please let us know in the comments. The code is almost identical like before, just with the second to last line changed. You can find the full code in the appendix at the end of this post.
Double Exponential
fit <- HoltWinters(timeser, gamma=FALSE)
Reverse Double Exponential
fit <- HoltWinters(timeser, beta=FALSE)
Triple Exponential
fit <- HoltWinters(timeser)
Now, let's look at these predictions.
Sales by Month (Predictions) |
Sales by Month (Bounds) |
Sales by Month (Reverse Double Exponential with Bounds) |
Brad Llewellyn
Data Analytics Consultant
Mariner, LLC
llewellyn.wb@gmail.com
https://www.linkedin.com/in/bradllewellyn
Data Analytics Consultant
Mariner, LLC
llewellyn.wb@gmail.com
https://www.linkedin.com/in/bradllewellyn
Appendix
Double Exponential
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser, gamma=FALSE)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$mean)
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Double Exponential (Lower 95%)
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser, gamma=FALSE)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$lower[,2])
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Double Exponential (Upper 95%)
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser, gamma=FALSE)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$upper[,2])
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Reverse Double Exponential
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser, beta=FALSE)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$mean)
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Reverse Double Exponential (Lower 95%)
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser, beta=FALSE)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$lower[,2])
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Reverse Double Exponential (Upper 95%)
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser, beta=FALSE)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$upper[,2])
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Triple Exponential
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$mean)
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Triple Exponential (Lower 95%)
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$lower[,2])
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Triple Exponential (Upper 95%)
SCRIPT_REAL("
library(forecast)
## Creating vectors
hold.orig <- .arg4
len.orig <- length( hold.orig )
len.new <- len.orig - hold.orig[1]
year.orig <- .arg1
month.orig <- .arg2
sales.orig <- .arg3
## Sorting the Data
date.orig <- year.orig + month.orig / 12
dat.orig <- cbind(year.orig, month.orig, sales.orig)[sort(date.orig, index.return = TRUE)$ix,]
dat.new <- dat.orig[1:len.new,]
## Fitting the Time Series
timeser <- ts(dat.new[,3], start = c(dat.new[1,1], dat.new[1,2]), end = c(dat.new[len.new,1], dat.new[len.new,2]), frequency = 12)
fit <- HoltWinters(timeser)
c(rep(NA,len.new), forecast(fit, hold.orig[1])$upper[,2])
"
, ATTR( YEAR( [Order Date] ) ), ATTR( MONTH( [Order Date] ) ), SUM( [Sales] ),
[Months to Forecast] )
Hi Brad,
ReplyDeleteThis example is highly relevant for me thank you so much for putting this together, just one question...
How would the R code change for a different date granularity, e.g. a week-level forecast?
Thanks! Alex