Revision 41f4487eeb92c7a33dcb841682ddf3a2bc825f20 authored by Rob J Hyndman on 09 May 2015, 00:00:00 UTC, committed by Gabor Csardi on 09 May 2015, 00:00:00 UTC
1 parent a968974
Raw File
tbats.R
tbats <- function(y, use.box.cox=NULL, use.trend=NULL, use.damped.trend=NULL, seasonal.periods=NULL, use.arma.errors=TRUE, use.parallel=TRUE, num.cores=2, bc.lower=0, bc.upper=1, ...) 
{
  if (any(class(y) %in% c("data.frame", "list", "matrix", "mts"))) 
    stop("y should be a univariate time series")
  y <- as.ts(y)
  ny <- length(y)
  y <- na.contiguous(y)
  if (ny != length(y)) 
    warning("Missing values encountered. Using longest contiguous portion of time series")
 
  if(is.constant(y))
  {
    fit <- list(y=y,x=matrix(y,nrow=1,ncol=ny),errors=y*0,fitted.values=y,seed.states=matrix(y[1]),
      AIC=-Inf,likelihood=-Inf,variance=0,alpha=0.9999, call=match.call())
    return(structure(fit,class='bats'))
  }

	if(any((y <= 0)))
		use.box.cox <- FALSE
		#stop("TBATS requires positive data")
	origy <- y
	non.seasonal.model <- bats(as.numeric(y), use.box.cox=use.box.cox, use.trend=use.trend, use.damped.trend=use.damped.trend, use.arma.errors=use.arma.errors, use.parallel=use.parallel, num.cores=num.cores, bc.lower=bc.lower, bc.upper=bc.upper, ...)

  # Get start time and seasonal periods
  if(is.null(seasonal.periods))
  {
    if(any(class(y) == "msts"))
      seasonal.periods <- attr(y,"msts")
    else if(class(y) == "ts")
      seasonal.periods <- frequency(y)
  }
  if(all(seasonal.periods == 1))
     seasonal.periods <- NULL
  start.time <- start(y)
  y <- as.numeric(y)

  if(is.null(seasonal.periods))
  {
		non.seasonal.model$call <- match.call()
		# Add ts attributes
		if(!any(class(origy) == "ts"))
    {
			if(is.null(seasonal.periods))
				origy <- ts(origy,start=1,frequency=1)
      else
				origy <- msts(origy,seasonal.periods)
		}
		attributes(non.seasonal.model$fitted.values) <- attributes(non.seasonal.model$errors) <- attributes(origy)
		non.seasonal.model$y <- origy
		return(non.seasonal.model)
	}
	else
  {
		seasonal.mask <- (seasonal.periods == 1)
		seasonal.periods <- seasonal.periods[!seasonal.mask]
	}
	if(is.null(use.box.cox)) {
		use.box.cox <- c(FALSE, TRUE)
	}
	if(any(use.box.cox)) {
		init.box.cox<-BoxCox.lambda(origy, lower=bc.lower, upper=bc.upper)
	} else {
		init.box.cox<-NULL
	}
	if(is.null(use.trend)) {
		use.trend <- c(FALSE, TRUE)
	} else if(use.trend == FALSE) {
		use.damped.trend <- FALSE
	}
	if(is.null(use.damped.trend)) {
		use.damped.trend <- c(FALSE, TRUE)
	}
	#Set a vector of model params for later comparison
	model.params <- logical(length=3)
	model.params[1] <- any(use.box.cox)
	model.params[2] <- any(use.trend)
	model.params[3] <- any(use.damped.trend)

	###The OLS setup
	#Get seasonal states
	#bats.states <- bats(y, model.params[1], model.params[2], model.params[3], seasonal.periods=seasonal.periods, force.seasonality=TRUE)$x
	#
#	if(model.params[2]) {
#		adj.beta <- 1
#	} else {
#		adj.beta <- 2
#	}
#	seasonals <- numeric(length(y)*length(seasonal.periods))
#	dim(seasonals) <- c(length(y), length(seasonal.periods))
#	previous.season <- 0
	k.vector <- rep(1, length(seasonal.periods))
	n <- length(y)
#	for(i in 1:length(seasonal.periods)) {
#		seasonals[,i] <- as.numeric(bats.states[(1+adj.beta+previous.season+seasonal.periods[i]),])
#		print((1+adj.beta+previous.season+seasonal.periods[i]))
#		p.val <- 0
#		fourier.terms <- makeSingleFourier(1, seasonal.periods[i], n)
#		previous.sse <- sum(residuals(lm(seasonals[,i] ~ fourier.terms -1))^2)
#		repeat {
#			if((2*(k.vector[i]+1)) >= (seasonal.periods[i]-1)) {
#				break
#			}
#			new.fourier.terms <- makeSingleFourier((k.vector[i]+1), seasonal.periods[i], n)
#			new.sse <- sum(residuals(lm(seasonals[,i] ~ fourier.terms + new.fourier.terms -1))^2)
#			p.val <- calcFTest(previous.sse, new.sse, 2, (2 + ncol(fourier.terms)), n)
#			if(p.val > .001) {
#				break
#			} else {
#				k.vector[i] <- k.vector[i]+1
#				four.terms <- cbind(four.terms, new.four.terms)
#			}
#		}
#		previous.season <- previous.season+seasonal.periods[i]
#	}

	#if(use.parallel) then make the cluster
	if(use.parallel) {
		if(is.null(num.cores)) {
			num.cores <- detectCores(all.tests = FALSE, logical = TRUE)
		}
		clus <- makeCluster(num.cores)
	}

	best.model <- fitSpecificTBATS(y, model.params[1], model.params[2], model.params[3], seasonal.periods, k.vector, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
	for(i in 1:length(seasonal.periods)) {
		if(seasonal.periods[i] == 2) {
			next
		}
		max.k <- floor(((seasonal.periods[i]-1)/2))
		if(i != 1) {
			current.k <- 2
			while(current.k <= max.k) {
				if(seasonal.periods[i]%%current.k != 0) {
					current.k <- current.k+1
					next
				}
				latter <- seasonal.periods[i]/current.k

				if(any(((seasonal.periods[1:(i-1)]%%latter) == 0))) {
					max.k <- current.k-1
					break
				} else {
					current.k <- current.k+1
				}

			}
		}
		#print("period")
		#print(seasonal.periods[i])
		#print("max.k")
		#print(max.k)

			if(max.k == 1) {
				next
			}
			if(max.k <= 6) {
				k.vector[i] <- max.k
				best.model$AIC <- Inf
				repeat {
					#old.k <- k.vector[i]
					#k.vector[i] <- k.vector[i]-1
					new.model <- fitSpecificTBATS(y, model.params[1], model.params[2], model.params[3], seasonal.periods, k.vector, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
					#print("6 or less")
					#print(k.vector)
					#print(i)

					if(new.model$AIC > best.model$AIC) {
						#print("6 or less")
						#print(k.vector)
						#print(i)
						k.vector[i] <- k.vector[i]+1
						break
					} else {
						if(k.vector[i] == 1) {
							#print("6 or less")
							#print(k.vector)
							#print(i)
							break
						}
						#old.k <- k.vector[i]
						k.vector[i] <- k.vector[i]-1
						best.model <- new.model
						#print("6 or less")
						#print(k.vector)
						#print(i)
					}

				}
				next
			} else {
				#Three different k vectors
				step.up.k <- k.vector
				step.down.k <- k.vector
				step.up.k[i] <- 7
				step.down.k[i] <- 5
				k.vector[i] <- 6
				#Fit three different models

				###if(use.parallel) then do parallel
				if(use.parallel) {
					k.control.array<-rbind(step.up.k, step.down.k, k.vector)
					#print(k.control.array)
					models.list <- clusterApplyLB(clus, c(1:3), parFitSpecificTBATS, y=y, box.cox=model.params[1], trend = model.params[2], damping = model.params[3], seasonal.periods = seasonal.periods, k.control.matrix=k.control.array, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
					up.model <- models.list[[1]]
					level.model <- models.list[[3]]
					down.model <- models.list[[2]]
				} else {
					up.model <- fitSpecificTBATS(y, model.params[1], model.params[2], model.params[3], seasonal.periods, step.up.k, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
					level.model <- fitSpecificTBATS(y, model.params[1], model.params[2], model.params[3], seasonal.periods, k.vector, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
					down.model <- fitSpecificTBATS(y, model.params[1], model.params[2], model.params[3], seasonal.periods, step.down.k, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
				}
				#Dcide the best model of the three and then follow that direction to find the optimal k
				aic.vector <- c(up.model$AIC, level.model$AIC, down.model$AIC)
				##If shifting down
				if(min(aic.vector) == down.model$AIC) {
					best.model <- down.model
					k.vector[i] <- 5
					repeat{
						k.vector[i] <- k.vector[i]-1
						down.model <- fitSpecificTBATS(y=y, use.box.cox=model.params[1], use.beta=model.params[2], use.damping=model.params[3], seasonal.periods=seasonal.periods, k.vector=k.vector, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
						#print("stepping down")
						#print(k.vector)
						#print(i)
						if(down.model$AIC > best.model$AIC) {
							k.vector[i] <- k.vector[i]+1
							break
						} else {
							best.model <- down.model
						}
						if(k.vector[i] == 1) {
							break
						}
					}
#					if(i == 1) {
#						prev.k <- c(1:k.vector[1])
#					} else {
#						prev.k <- c(prev.k, 1:k.vector[i])
#					}
				##If staying level
				} else if(min(aic.vector) == level.model$AIC) {
					best.model <- level.model
					#print("staying level")
					#print(k.vector)
					#print(i)
					next
				##If shifting up
				} else {
					best.model <- up.model
					k.vector[i] <- 7
					repeat {
						k.vector[i] <- k.vector[i]+1
						up.model <- fitSpecificTBATS(y, model.params[1], model.params[2], model.params[3], seasonal.periods, k.vector, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
						#print("stepping up")
						#print(k.vector)
						#print(i)
						if(up.model$AIC > best.model$AIC) {
							k.vector[i] <- k.vector[i]-1
							break
						} else {
							best.model <- up.model
						}
						if(k.vector[i] == max.k) {
							break
						}
					}
				}


			}


	}
	aux.model <- best.model

	if(non.seasonal.model$AIC < best.model$AIC) {
		best.model <- non.seasonal.model
	}
	
	if((length(use.box.cox) == 1) & (use.trend[1] == TRUE) & (length(use.trend) == 1) & (length(use.damped.trend) == 1) & (use.parallel)) {
		#In the this case, there is only one alternative.
		use.parallel <- FALSE
		stopCluster(clus)
	} else if((length(use.box.cox) == 1) & (use.trend[1] == FALSE) & (length(use.trend) == 1) & (use.parallel)) {
		#As above, in the this case, there is only one alternative.
		use.parallel <- FALSE
		stopCluster(clus)
	}
	
	if(use.parallel) {
		#Set up the control array
		control.array <- NULL
		for(box.cox in use.box.cox) {
			for(trend in use.trend) {
				for(damping in use.damped.trend) {
					if((trend == FALSE) & (damping == TRUE)) {
						next
					}
					control.line <- c(box.cox, trend, damping)
					if(!is.null(control.array)) {
						control.array <- rbind(control.array, control.line)
					} else {
						control.array <- control.line
					}
				}
			}
		}
		##Fit the models
		#if(is.null(num.cores)) {
		#	num.cores <- detectCores(all.tests = FALSE, logical = TRUE)
		#}
		#clus <- makeCluster(num.cores)
		models.list <- clusterApplyLB(clus, c(1:nrow(control.array)), parFilterTBATSSpecifics, y=y, control.array=control.array, model.params=model.params, seasonal.periods=seasonal.periods, k.vector=k.vector, use.arma.errors=use.arma.errors, aux.model=aux.model, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper, ...)
		stopCluster(clus)
		##Choose the best model
		####Get the AICs
		aics <- numeric(nrow(control.array))
		for(i in 1:nrow(control.array)) {
			aics[i] <- models.list[[i]]$AIC
		}
		best.number <- which.min(aics)
		best.seasonal.model <- models.list[[best.number]]
		if(best.seasonal.model$AIC < best.model$AIC) {
			best.model <- best.seasonal.model
		}

	} else {
		for(box.cox in use.box.cox) {
			for(trend in use.trend) {
				for(damping in use.damped.trend) {
					if(all((model.params == c(box.cox, trend, damping)))) {
						new.model <- filterTBATSSpecifics(y, box.cox, trend, damping, seasonal.periods, k.vector, use.arma.errors, aux.model=aux.model, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper, ...)
					} else if(!((trend == FALSE) & (damping == TRUE))) {
						new.model <- filterTBATSSpecifics(y, box.cox, trend, damping, seasonal.periods, k.vector, use.arma.errors, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper, ...)
					}
					if(new.model$AIC < best.model$AIC) {
						best.model <- new.model
					}

				}
			}
		}
	}

	best.model$call <- match.call()
	#best.model$start.time <- start.time
	# Add ts attributes
	if(!any(class(origy) == "ts")) {
		if(is.null(seasonal.periods)) {
			origy <- ts(origy,start=1,frequency=1)
		} else {
			origy <- msts(origy,seasonal.periods)
		}
	}
	attributes(best.model$fitted.values) <- attributes(best.model$errors) <- attributes(origy)
	best.model$y <- origy

	return(best.model)
}

######################################################################################################################################
parFilterTBATSSpecifics <- function(control.number, y, control.array, model.params, seasonal.periods, k.vector, use.arma.errors, aux.model=NULL, init.box.cox=NULL, bc.lower=0, bc.upper=1, ...) {
	box.cox <- control.array[control.number, 1]
	trend <- control.array[control.number, 2]
	damping <- control.array[control.number, 3]
	if(!all((model.params == c(box.cox, trend, damping)))) {
		first.model <- fitSpecificTBATS(y, use.box.cox=box.cox, use.beta=trend, use.damping=damping, seasonal.periods=seasonal.periods, k.vector=k.vector, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
	} else {
		first.model <- aux.model
	}

	if(use.arma.errors) {
		arma <- try(auto.arima(as.numeric(first.model$errors), d=0, ...), silent=TRUE)
		if(!is.element("try-error",class(arma))) {
			p <- arma$arma[1]
			q <- arma$arma[2]
			if((p != 0) | (q != 0)) { #Did auto.arima() find any AR() or MA() coefficients?
				if(p != 0) {
					ar.coefs <- numeric(p)
				} else {
					ar.coefs <- NULL
				}
				if(q != 0) {
					ma.coefs <- numeric(q)
				} else {
					ma.coefs <- NULL
				}
				starting.params <- first.model$parameters

				second.model <- fitSpecificTBATS(y, use.box.cox=box.cox, use.beta=trend, use.damping=damping, seasonal.periods=seasonal.periods, k.vector=k.vector, ar.coefs=ar.coefs, ma.coefs=ma.coefs, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
				if(second.model$AIC < first.model$AIC) {
					return(second.model)
				} else {
					return(first.model)
				}
			} else { #Else auto.arima() did not find any AR() or MA()coefficients
				return(first.model)
			}
		} else {
			return(first.model)
		}
	} else {
		return(first.model)
	}
}

#################################################################################################
parFitSpecificTBATS <- function(control.number, y, box.cox, trend, damping, seasonal.periods, k.control.matrix, init.box.cox=NULL, bc.lower=0, bc.upper=1) {
	k.vector<-k.control.matrix[control.number,]
	return(fitSpecificTBATS(y, box.cox, trend, damping, seasonal.periods, k.vector, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper))
}

filterTBATSSpecifics <- function(y, box.cox, trend, damping, seasonal.periods, k.vector, use.arma.errors, aux.model=NULL, init.box.cox=NULL, bc.lower=0, bc.upper=1, ...) {
	if(is.null(aux.model)) {
		first.model <- fitSpecificTBATS(y, use.box.cox=box.cox, use.beta=trend, use.damping=damping, seasonal.periods=seasonal.periods, k.vector=k.vector, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
	} else {
		first.model <- aux.model
	}
	if(use.arma.errors) {
		arma <- try(auto.arima(as.numeric(first.model$errors), d=0, ...), silent=TRUE)
		if(!is.element("try-error",class(arma))) {
			p <- arma$arma[1]
			q <- arma$arma[2]
			if((p != 0) | (q != 0)) { #Did auto.arima() find any AR() or MA() coefficients?
				if(p != 0) {
					ar.coefs <- numeric(p)
				} else {
					ar.coefs <- NULL
				}
				if(q != 0) {
					ma.coefs <- numeric(q)
				} else {
					ma.coefs <- NULL
				}
				starting.params <- first.model$parameters

				second.model <- fitSpecificTBATS(y, use.box.cox=box.cox, use.beta=trend, use.damping=damping, seasonal.periods=seasonal.periods, k.vector=k.vector, ar.coefs=ar.coefs, ma.coefs=ma.coefs, init.box.cox=init.box.cox, bc.lower=bc.lower, bc.upper=bc.upper)
				if(second.model$AIC < first.model$AIC) {
					return(second.model)
				} else {
					return(first.model)
				}
			} else { #Else auto.arima() did not find any AR() or MA()coefficients
				return(first.model)
			}
		} else {
			return(first.model)
		}
	} else {
		return(first.model)
	}
}


makeSingleFourier <- function(j, m, T) {
	frier <- matrix(0, nrow=T, ncol=2)
	for(t in 1:T) {
		frier[t,1] <- cos((2*pi*j)/m)
		frier[t,2] <- sin((2*pi*j)/m)
	}
	return(frier)
}

calcFTest <- function(r.sse, ur.sse, num.restrictions, num.u.params, num.observations) {
	f.stat <- ((r.sse - ur.sse)/num.restrictions)/(r.sse/(num.observations - num.u.params))
	p.value <- pf(f.stat, num.restrictions, (num.observations - num.u.params),lower.tail=FALSE )
	return(p.value)
}


print.tbats <- function(x, ...) {
	cat(makeTextTBATS(x))
	cat("\n")
	cat("\nCall: ")
	print(x$call)
	cat("\nParameters")
  if(!is.null(x$lambda))
  {
    cat("\n  Lambda: ")
    cat(round(x$lambda,6))
  }
	cat("\n  Alpha: ")
	cat(x$alpha)
  if(!is.null(x$beta))
  {
    cat("\n  Beta: ")
    cat(x$beta)
    cat("\n  Damping Parameter: ")
    cat(round(x$damping.parameter,6))
  }
  if(!is.null(x$gamma.one.values))
  {
    cat("\n  Gamma-1 Values: ")
    cat(x$gamma.one.values)
  }
  if(!is.null(x$gamma.two.values))
  {
    cat("\n  Gamma-2 Values: ")
    cat(x$gamma.two.values)
  }
  if(!is.null(x$ar.coefficients))
  {
    cat("\n  AR coefficients: ")
    cat(round(x$ar.coefficients,6))
  }
  if(!is.null(x$ma.coefficients))
  {
    cat("\n  MA coefficients: ")
    cat(round(x$ma.coefficients,6))
  }
	cat("\n")
	cat("\nSeed States:\n")
	print(x$seed.states)
	cat("\nSigma: ")
	cat(sqrt(x$variance))
	cat("\nAIC: ")
	cat(x$AIC)
	cat("\n")
}



plot.tbats <- function (x, main="Decomposition by TBATS model", ...)
{
	out <- tbats.components(x)
	plot(out, main=main, nc=1, ...)
}


tbats.components <- function(x)
{
  # Get original data, transform if necessary
  if (!is.null(x$lambda))
    y <- BoxCox(x$y, x$lambda)
  else
    y <- x$y
  # Compute matrices
  tau <- ifelse(!is.null(x$k.vector), 2*sum(x$k.vector), 0)
	w <- .Call("makeTBATSWMatrix", smallPhi_s = x$damping.parameter, kVector_s=as.integer(x$k.vector), 
		arCoefs_s = x$ar.coefficients, maCoefs_s = x$ma.coefficients, tau_s=as.integer(tau), PACKAGE = "forecast")

  out <- cbind(observed=c(y), level=x$x[1,])
  if(!is.null(x$beta))
    out <- cbind(out, slope=x$x[2,])

  # Add seasonal components if they exist
  if(tau > 0)
  {
	  nonseas <- 2+!is.null(x$beta) # No. non-seasonal columns in out
  	nseas <- length(x$seasonal.periods) # No. seasonal periods
	  seas.states <- cbind(x$seed.states,x$x)[-(1:(1+!is.null(x$beta))),]
  	seas.states <- seas.states[,-ncol(seas.states)]
	  w <- w$w.transpose[,-(1:(1+!is.null(x$beta))),drop=FALSE]
  	w <- w[,1:tau,drop=FALSE]
  	j <- cumsum(c(1,2*x$k.vector))
  	for(i in 1:nseas)
    	out <- cbind(out, season=c(w[,j[i]:(j[i+1]-1),drop=FALSE] %*% seas.states[j[i]:(j[i+1]-1),]))
  	if(nseas > 1)
    	colnames(out)[nonseas + 1:nseas] <- paste("season",1:nseas,sep="")
  }

  # Add time series characteristics
  out <- ts(out)
  tsp(out) <- tsp(y)
  return(out)
}
back to top