Nothing
###############################################
##### Forecasting Using Smoothing Splines #####
###############################################
# Optimal smoothing paramter denoted by beta
# lambda is Box-Cox parameter.
################# FUNCTIONS ##################
## Set up Sigma of order (n x n)
make.Sigma <- function(n, n0=0) {
nn <- n + n0
Sigma <- matrix(0, nrow = nn, ncol = nn)
for (i in 1:nn)
Sigma[i, i:nn] <- Sigma[i:nn, i] <- (i * i * (3 * (i:nn) - i)) / 6
return(Sigma / (n ^ 3))
}
## Compute spline matrices
spline.matrices <- function(n, beta, cc=1e2, n0=0) {
nn <- n + n0
Sigma <- make.Sigma(n, n0)
s <- cbind(rep(1, nn), (1:nn) / n)
Omega <- cc * s %*% t(s) + Sigma / beta + diag(nn)
max.Omega <- max(Omega)
inv.Omega <- solve(Omega / max.Omega, tol = 1e-10) / max.Omega
P <- chol(inv.Omega)
return(list(s = s, Sigma = Sigma, Omega = Omega, inv.Omega = inv.Omega, P = P))
}
## Compute smoothing splines
## Return -loglikelihood
# beta multiplied by 1e6 to avoid numerical difficulties in optimization
spline.loglik <- function(beta, y, cc=1e2) {
n <- length(y)
mat <- spline.matrices(n, beta / 1e6, cc = cc)
y.star <- mat$P %*% matrix(y)
return(-log(det(mat$P)) + 0.5 * n * log(sum(y.star ^ 2)))
}
# Spline forecasts
#' Cubic Spline Forecast
#'
#' Returns local linear forecasts and prediction intervals using cubic
#' smoothing splines.
#'
#' The cubic smoothing spline model is equivalent to an ARIMA(0,2,2) model but
#' with a restricted parameter space. The advantage of the spline model over
#' the full ARIMA model is that it provides a smooth historical trend as well
#' as a linear forecast function. Hyndman, King, Pitrun, and Billah (2002) show
#' that the forecast performance of the method is hardly affected by the
#' restricted parameter space.
#'
#' @param y a numeric vector or time series of class \code{ts}
#' @param h Number of periods for forecasting
#' @param level Confidence level for prediction intervals.
#' @param fan If TRUE, level is set to seq(51,99,by=3). This is suitable for
#' fan plots.
#' @param method Method for selecting the smoothing parameter. If
#' \code{method="gcv"}, the generalized cross-validation method from
#' \code{\link[stats]{smooth.spline}} is used. If \code{method="mle"}, the
#' maximum likelihood method from Hyndman et al (2002) is used.
#' @param x Deprecated. Included for backwards compatibility.
#' @inheritParams forecast.ts
#' @return An object of class "\code{forecast}".
#'
#' The function \code{summary} is used to obtain and print a summary of the
#' results, while the function \code{plot} produces a plot of the forecasts and
#' prediction intervals.
#'
#' The generic accessor functions \code{fitted.values} and \code{residuals}
#' extract useful features of the value returned by \code{splinef}.
#'
#' An object of class \code{"forecast"} containing the following elements:
#' \item{model}{A list containing information about the fitted model}
#' \item{method}{The name of the forecasting method as a character string}
#' \item{mean}{Point forecasts as a time series} \item{lower}{Lower limits for
#' prediction intervals} \item{upper}{Upper limits for prediction intervals}
#' \item{level}{The confidence values associated with the prediction intervals}
#' \item{x}{The original time series (either \code{object} itself or the time
#' series used to create the model stored as \code{object}).}
#' \item{onestepf}{One-step forecasts from the fitted model.}
#' \item{fitted}{Smooth estimates of the fitted trend using all data.}
#' \item{residuals}{Residuals from the fitted model. That is x minus one-step
#' forecasts.}
#' @author Rob J Hyndman
#' @seealso \code{\link[stats]{smooth.spline}}, \code{\link[stats]{arima}},
#' \code{\link{holt}}.
#' @references Hyndman, King, Pitrun and Billah (2005) Local linear forecasts
#' using cubic smoothing splines. \emph{Australian and New Zealand Journal of
#' Statistics}, \bold{47}(1), 87-99.
#' \url{https://robjhyndman.com/publications/splinefcast/}.
#' @keywords ts
#' @examples
#' fcast <- splinef(uspop,h=5)
#' plot(fcast)
#' summary(fcast)
#'
#' @export
splinef <- function(y, h=10, level=c(80, 95), fan=FALSE, lambda=NULL, biasadj=FALSE, method=c("gcv", "mle"), x=y) {
method <- match.arg(method)
if (!is.ts(x)) {
x <- ts(x)
}
n <- length(x)
freq <- frequency(x)
if (!is.null(lambda)) {
origx <- x
x <- BoxCox(x, lambda)
lambda <- attr(x, "lambda")
}
# Find optimal beta using likelihood approach in Hyndman et al paper.
if (method == "mle") {
if (n > 100) { # Use only last 100 observations to get beta
xx <- x[(n - 99):n]
} else {
xx <- x
}
beta.est <- optimize(spline.loglik, interval = c(1e-6, 1e7), y = xx)$minimum / 1e6
# Compute spar which is equivalent to beta
r <- 256 * smooth.spline(1:n, x, spar = 0)$lambda
lss <- beta.est * n ^ 3 / (n - 1) ^ 3
spar <- (log(lss / r) / log(256) + 1) / 3
splinefit <- smooth.spline(1:n, x, spar = spar)
sfits <- splinefit$y
}
else # Use GCV
{
splinefit <- smooth.spline(1:n, x, cv = FALSE, spar = NULL)
sfits <- splinefit$y
beta.est <- pmax(1e-7, splinefit$lambda * (n - 1) ^ 3 / n ^ 3)
}
# Compute matrices for optimal beta
mat <- spline.matrices(n, beta.est)
newmat <- spline.matrices(n, beta.est, n0 = h)
# Get one-step predictors
yfit <- e <- rep(NA, n)
if (n > 1000) {
warning("Series too long to compute training set fits and residuals")
} else # This is probably grossly inefficient but I can't think of a better way right now
{
for (i in 1:(n - 1))
{
U <- mat$Omega[1:i, i + 1]
Oinv <- solve(mat$Omega[1:i, 1:i] / 1e6) / 1e6
yfit[i + 1] <- t(U) %*% Oinv %*% x[1:i]
sd <- sqrt(mat$Omega[i + 1, i + 1] - t(U) %*% Oinv %*% U)
e[i + 1] <- (x[i + 1] - yfit[i + 1]) / sd
}
}
# Compute sigma^2
sigma2 <- mean(e ^ 2, na.rm = TRUE)
# Compute mean and var of forecasts
U <- newmat$Omega[1:n, n + (1:h)]
Omega0 <- newmat$Omega[n + (1:h), n + (1:h)]
Yhat <- t(U) %*% mat$inv.Omega %*% x
sd <- sqrt(sigma2 * diag(Omega0 - t(U) %*% mat$inv.Omega %*% U))
# Compute prediction intervals.
if (fan) {
level <- seq(51, 99, by = 3)
} else {
if (min(level) > 0 && max(level) < 1) {
level <- 100 * level
} else if (min(level) < 0 || max(level) > 99.99) {
stop("Confidence limit out of range")
}
}
nconf <- length(level)
lower <- upper <- matrix(NA, nrow = h, ncol = nconf)
for (i in 1:nconf)
{
conf.factor <- qnorm(0.5 + 0.005 * level[i])
upper[, i] <- Yhat + conf.factor * sd
lower[, i] <- Yhat - conf.factor * sd
}
lower <- ts(lower, start = tsp(x)[2] + 1 / freq, frequency = freq)
upper <- ts(upper, start = tsp(x)[2] + 1 / freq, frequency = freq)
res <- ts(x - yfit, start = start(x), frequency = freq)
if (!is.null(lambda)) {
Yhat <- InvBoxCox(Yhat, lambda, biasadj, list(level = level, upper = upper, lower = lower))
upper <- InvBoxCox(upper, lambda)
lower <- InvBoxCox(lower, lambda)
yfit <- InvBoxCox(yfit, lambda)
sfits <- InvBoxCox(sfits, lambda)
x <- origx
}
return(structure(
list(
method = "Cubic Smoothing Spline", level = level, x = x,
series = deparse(substitute(y)),
mean = ts(Yhat, frequency = freq, start = tsp(x)[2] + 1 / freq),
upper = ts(upper, start = tsp(x)[2] + 1 / freq, frequency = freq),
lower = ts(lower, start = tsp(x)[2] + 1 / freq, frequency = freq),
model = list(beta = beta.est * n ^ 3, call = match.call()),
fitted = ts(sfits, start = start(x), frequency = freq), residuals = res,
standardizedresiduals = ts(e, start = start(x), frequency = freq),
onestepf = ts(yfit, start = start(x), frequency = freq)
),
lambda = lambda,
class = c("splineforecast", "forecast")
))
}
#' @rdname plot.forecast
#'
#' @examples
#' fcast <- splinef(airmiles,h=5)
#' plot(fcast)
#' autoplot(fcast)
#'
#' @export
plot.splineforecast <- function(x, fitcol=2, type="o", pch=19, ...) {
plot.forecast(x, type = type, pch = pch, ...)
lines(x$fitted, col = fitcol)
}
#' @rdname is.forecast
#' @export
is.splineforecast <- function(x) {
inherits(x, "splineforecast")
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.