#' Estimate the parameters of a GARMA model.
#'
#' The garma function is the main function for the garma package. Depending on the parameters it will
#' calculate the parameter estimates for the GARMA process, and if available the standard errors (se's)
#' for those parameters.
#'
#' The GARMA model is specified as
#' \deqn{\displaystyle{\phi(B)\prod_{i=1}^{k}(1-2u_{i}B+B^{2})^{d_{i}}(1-B)^{id} (X_{t}-\mu)= \theta(B) \epsilon _{t}}}{\prod(i=1 to k) (1-2u(i)B+B^2)^d(i) \phi(B)(1-B)^{id} (X(t) - \mu) = \theta(B) \epsilon(t)}
#'
#' where
#' \itemize{
#' \item \eqn{\phi(B)}{\phi(B)} represents the short-memory Autoregressive component of order p,
#' \item \eqn{\theta(B)}{\theta(B)} represents the short-memory Moving Average component of order q,
#' \item \eqn{(1-2u_{i}B+B^{2})^{d_{i}}}{(1-2u(i)B+B^2)^d(i)} represents the long-memory Gegenbauer component (there may in
#' general be k of these),
#' \item \eqn{id} represents the degree of integer differencing, where as \eqn{d_i} represents the degree of fractional
#' differencing. Note that \eqn{id} is a value supplied by the user (the second number on the `order=` parameter - similarly
#' to the way that the base R `arima` function works) whereas \eqn{d_i} is estimated by this function.
#' \item \eqn{X_{t}}{X(t)} represents the observed process,
#' \item \eqn{\epsilon_{t}}{\epsilon(t)} represents the random component of the model - these are assumed to be uncorrelated but
#' identically distributed variates. Generally the routines in this package will work best if these have an approximate
#' Gaussian distribution.
#' \item \eqn{B}{B} represents the Backshift operator, defined by \eqn{B X_{t}=X_{t-1}}{B X(t) = X(t-1)}.
#' }
#' when k=0, then this is just a short memory model as fit by the stats "arima" function.
#'
#' @param x (num) This should be a numeric vector representing the process to estimate. A minimum length of 96 is required.
#' @param order (numeric vector) This should be a vector (similar to the stats::arima order parameter) which will give the order
#' of the process to fit. The format should be list(p,d,q) where p, d, and q are all positive integers. p represents the degree
#' of the autoregressive process to fit, q represents the order of the moving average process to fit and d is the (integer)
#' differencing to apply prior to any fitting. WARNING: Currently only d==0 or d==1 are allowed.
#' @param periods (num) This parameter can be used to specify a fixed period or set of periods for the
#' Gegenbauer periodicity. For instance if you have monthly data, then it might be sensible (after an examination of the
#' periodogram) to set periods = 12. The default value is NULL. Either `periods` or `k` parameters must be specified
#' but not both - `periods` implies fixed period(s) are to be used and `k` implies that the periods should
#' be estimated.
#' @param k (int) This parameter indicates that the algorithm should estimate the `k` frequencies as a part of the model.
#' An alternative is the `periods` parameter which can be used to specify exactly which periods should be used by
#' the model.
#'
#' This parameter can also be interpreted as specifying the number of (multiplicative) Gegenbauer terms to fit in the model.
#' @param include.mean (bool) A boolean value indicating whether a mean should be fit.
#' Note that no mean term is fit if the series is integer differenced.
#' @param include.drift (bool) A boolean value indicating whether a 'drift' term should be fit to the predictions.
#' The default is to fit a drift term to the predictions if the process is integer-differenced.
#' @param xreg (numeric matrix) A numerical vector or matrix of external regressors, which must have the same number of rows as x.
#' It should not have any NA values. It should not be a data frame. The default value is NULL.
#'
#' Note that the algorithm used here is that if any `xreg` is supplied, then a linear regression model is fit first, and the
#' GARMA model is then based on the residuals from that regression model.
#' @param method (character) This defines the estimation method for the routine. The valid values are 'CSS', 'Whittle', and
#' 'WLL'. The default ('Whittle') method will generally return very accurate estimates quite quickly, provided the assumption
#' of a Gaussian distribution is even approximately correct, and is probably the method of choice for most users. For the
#' theory behind this, refer Giraitis et. al. (2001).
#'
#' The 'CSS' method is a conditional 'sum-of-squares' technique and can be quite slow.
#' Reference: Robinson (2006), Chung (1996). Note that the paper of Chung (1996) was partially critisised by Giraitis et.
#' al. (2001), however still contains useful results.
#'
#' 'WLL' is a new technique, originally developed by the author of this package and which appears to work well even if the
#' \eqn{\epsilon_{t}}{\epsilon(t)} are highly skewed and/or have heavy tails (skewed and/or lepto-kurtic). However the
#' asymptotic theory for the WLL method is not complete and so standard errors are not available for most parameters.
#' Refer Hunt et. al. (2021).
#' @param d_lim (list) the limits for the d parameter. The default is `c(0,0.5)`, which restricts the model to be stationary.
#' However sometimes it is desirable to understand what the unrestricted value might be.
#' @param opt_method (character) This names the optimisation method used to find the parameter estimates.
#' This may be a list of methods, in which case the methods are applied in turn,
#' each using the results of the previous one as the starting point for the next. The default is to use c('solnp', 'cobyla').
#' For some data or some models, however, other methods may work well.
#'
#' Supported algorithms include:
#' \itemize{
#' \item 'cobyla' algorithm in package nloptr
#' \item 'directL' algorithm in package nloptr
#' \item 'solnp' from Rsolnp package
#' \item 'gosolnp' from Rsolnp package.
#' }
#'
#' Note that the algorithms are selected to be those which do not require derivatives, even numerically calculated
#' derivatives. The function being optimised by `garma()` has a point of discontinuity at the minimum value - the point
#' we are trying to find. This means that standard algorithms like BFGS et al. perform very poorly here.
#'
#' Note further that if you specify a value of `k` > 1, then inequality constraints are required, and this will further limit
#' the list of supported routines.
#' @param control (list) list of optimisation routine specific values.
#' @return An S3 object of class "garma_model".
#'
#' @references
#' C Chung. A generalized fractionally integrated autoregressive moving-average process.
#' Journal of Time Series Analysis, 17(2):111-140, 1996. DOI: https://doi.org/10.1111/j.1467-9892.1996.tb00268.x
#'
#' L Giraitis, J Hidalgo, and P Robinson. Gaussian estimation of parametric spectral density with unknown pole.
#' The Annals of Statistics, 29(4):987–1023, 2001. DOI: https://doi.org/10.1214/AOS/1013699989
#'
#' R Hunt, S Peiris, N Webe. A General Frequency Domain Estimation Method for Gegenbauer Processes.
#' Journal of Time Series Econometrics, 13(2):119-144, 2021. DOI: https://doi.org/10.1515/jtse-2019-0031
#'
#' R Hunt, S Peiris, N Weber. Estimation methods for stationary Gegenbauer processes.
#' Statistical Papers 63:1707-1741, 2022. DOI: https://doi.org/10.1007/s00362-022-01290-3
#'
#' P. Robinson. Conditional-sum-of-squares estimation of models for stationary time series with long memory.
#' IMS Lecture Notes Monograph Series, Time Series and Related Topics, 52:130-137, 2006.
#' DOI: https://doi.org/10.1214/074921706000000996.
#'
#' @examples
#' data(AirPassengers)
#' ap <- as.numeric(diff(AirPassengers, 12))
#' print(garma(ap, order = c(9, 1, 0), k = 0, method = "CSS", include.mean = FALSE))
#' # Compare with the built-in arima function
#' print(arima(ap, order = c(9, 1, 0), include.mean = FALSE))
#' @export
garma <- function(x,
order = c(0L, 0L, 0L),
periods = NULL,
k = 1,
include.mean = (order[2] == 0L),
include.drift = FALSE,
xreg = NULL,
method = "Whittle",
d_lim = c(0, 0.5),
opt_method = c("cobyla", "solnp"),
control = NULL) {
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check parameters
# Check x
if (is.data.frame(x)) {
if (ncol(x) > 1) {
rlang::abort("x should be a numeric vector - not an entire data frame. Please select a single column and try again.")
} else {
x <- x[, 1]
}
}
if (length(x) < 96) {
# This is a bit arbitary but I would be concerned about fitting to a process of length even 96.
# But no real evidence for this restriction apart from simulations showing large std errs.
rlang::abort("x should have at least 96 observations.")
}
# now save the ts elements, if any - we re-apply them to the output values
x_start <- stats::start(x)
x_end <- stats::end(x)
x_freq <- stats::frequency(x)
x <- as.numeric(x)
if (!is.numeric(x)) {
rlang::abort("x should be numeric or a ts object.\n")
}
if (any(is.na(x))) {
rlang::abort("x should not have any missing values.\n")
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check periods
if (is.null(periods)) {
if (is.null(k) | !is.numeric(k) | k < 0) {
rlang::abort("The k parameter must be a non-negative integer.\n")
}
} else {
if (!is.numeric(periods) | length(periods) == 0) {
rlang::abort("The 'periods' parameter must be a numeric vector of at least 1 element.")
}
if (any(periods < 0)) {
rlang::abort("The 'periods' parameter cannot contain negative values.")
}
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check 'order'
if (length(order) != 3) {
rlang::abort("The 'order' parameter must be a 3 integers only.\n")
}
if (any(order < 0)) {
rlang::abort("The 'order' parameter must consist of positive integers.\n")
}
if (order[1] + order[3] + k + length(periods) <= 0) {
rlang::abort("At least one of p, q or k (or periods) must be positive.\n")
}
if (order[2] > 0 & include.mean) {
rlang::warn("The parameter 'include.mean' is ignored since integer differencing is specified in the order= parameter.")
include.mean <- FALSE
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check xreg
if (!is.null(xreg)) {
if (!is.numeric(xreg)) {
rlang::abort("The parameter 'xreg' should be a numeric vector or matrix.")
}
if (!is.vector(xreg) & ! is.matrix(xreg)) {
rlang::abort("The parameter 'xreg' shoud be a vector or a matrix. Dataframes are specifically not supported.")
}
if (is.vector(xreg)) {
if (length(xreg) != length(x)) {
rlang::abort("The parameter 'xreg' should be the same length as 'x'.")
}
# convert vector to matrix
xreg_name <- as.character(deparse(substitute(xreg)))
xreg <- as.matrix(xreg)
colnames(xreg) <- internal_make_column_name(xreg_name)
} else if (is.matrix(xreg)) {
if (nrow(xreg) != length(x)) {
rlang::abort("The parameter 'xreg' should have the same number of rows as the length of 'x'.")
}
}
if (any(is.na(xreg))) {
rlang::abort("The parameter 'xreg' should not have any NA values.")
}
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check estimation method
allowed_methods <- c("CSS", "Whittle", "WLL")
if (!method %in% allowed_methods) {
rlang::abort("The parameter 'method' must be one of CSS, Whittle or WLL.\n")
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check optimisation method
unrecognised_opt_method <- setdiff(opt_method, internal_supported_optim())
if (length(unrecognised_opt_method) > 0) {
rlang::abort(
paste0("The following optimisation routines are not supported: ",
paste(unrecognised_opt_method, collapse = ", "), ".")
)
}
rm(unrecognised_opt_method)
optimisation_packages <- internal_optim_packages()
required_opt_packages <- sapply(opt_method, function(x) optimisation_packages[[x]])
required_opt_packages <- unique(required_opt_packages)
packages_loaded <- sapply(required_opt_packages, isNamespaceLoaded)
if (any(!packages_loaded)) {
idx <- which(!packages_loaded)
rlang::abort(paste0("The following packages are required but are not available: ", paste(names(packages_loaded)[idx], collapse = ", "), "."))
}
rm(optimisation_packages, required_opt_packages, packages_loaded)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# check d_lim
if (any(is.na(d_lim))) {
rlang::abort("The parameter 'd_lim' should not have any NA values.")
}
if (!is.numeric(d_lim) | length(d_lim) != 2 | d_lim[1] > d_lim[2]) {
rlang::abort("The parameter 'd_lim' should be a list of 2 numerics, Eg c(0,0.5) and the minimum should be < maximum.\n")
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set up a default 'control'
if (missing(control) | is.null(control)) {
control <- list(tol = 1e-8, maxeval = 10000, max_eval = 10000, maxit = 10000, trace = 0, delta = 1e-8,
inner.iter = 100, outer.iter = 100)
}
result <- list(
"call" = match.call(),
"series" = deparse(match.call()$x),
"method" = method,
"opt_method" = opt_method,
"control" = control,
"order" = order,
"k" = k,
"periods" = periods,
"y" = x,
"y_start" = x_start,
"y_end" = x_end,
"y_freq" = x_freq,
"include.mean" = include.mean,
"include.drift" = include.drift,
"xreg" = xreg,
"d_lim" = d_lim,
"garma_version" = as.character(utils::packageVersion("garma"))
)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set up data for calculating parameter estimates
p <- as.integer(order[1])
d <- as.integer(order[2])
q <- as.integer(order[3])
storage.mode(x) <- "double"
if (!is.null(xreg)) {
# ensure we have column names for xreg
if (is.null(colnames(xreg))) colnames(xreg) <- paste0("x", 1:ncol(xreg))
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# mean term
if (include.mean) {
mean_matrix <- matrix(data=rep(1,length(x)), ncol = 1)
colnames(mean_matrix) <- "intercept"
if (!is.null(xreg)) {
xreg <- cbind(mean_matrix, xreg)
} else {
xreg <- mean_matrix
}
rm(mean_matrix)
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# drift term
result$lm_xreg <- lm1 <- NULL
if (include.drift) {
drift_matrix = matrix(data = 1:length(x), ncol = 1)
colnames(drift_matrix) <- "drift"
if (!is.null(xreg)) {
xreg <- cbind(drift_matrix, xreg)
} else {
xreg <- drift_matrix
}
rm(drift_matrix)
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# xreg estimate
if (!is.null(xreg)) {
df <- as.data.frame(xreg)
df$temp_y_ <- x
f <- stats::formula(paste0("temp_y_ ~ ", paste(colnames(xreg), collapse = "+"), "- 1"))
result$lm_xreg <- lm1 <- stats::lm(f, data = df, model = FALSE)
rm(df, f)
# now we use the residuals for the time series model.
y <- as.numeric(lm1$residuals)
coef <- lm1$coefficients
se <- sqrt(diag(vcov(lm1)))
} else {
y <- x
coef <- numeric(0)
se <- numeric(0)
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# First adjust for integer differencing
if (d > 0) {
y <- diff(y, differences = d)
} else {
y <- y
}
# save this away in the result object
result$diff_y <- y
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Now set up params and lb (lower bounds) and ub (upper bounds)
pars <- numeric(0)
lb <- numeric(0)
ub <- numeric(0)
sd_y <- stats::sd(y)
ss <- internal_garma_pgram(y) # periodogram
# First, Gegenbauer pars
if (is.null(periods)) {
# need to estimate both periods and fractional differencing
gf <- internal_semipara(y, k = k, pgram = ss)
for (k1 in seq_len(k)) {
gf1 <- gf[[k1]]
start_u <- cos(2*pi*gf1$freq)
start_d <- gf1$fd
max_u <- 1 - 1e-15
min_u <- 1e-15
if (is.null(start_u)) start_u <- (min_u + max_u) / 2
if (is.na(start_u)) start_u <- (min_u + max_u) / 2
if (start_u < min_u | start_u > max_u) start_u <- (min_u + max_u) / 2
if (is.null(start_d)) start_d <- mean(d_lim)
if (is.na(start_d)) start_d <- mean(d_lim)
if (start_d >= d_lim[2]) start_d <- d_lim[2] - 0.01
if (start_d <= d_lim[1]) start_d <- d_lim[1] + 0.01
pars <- c(pars, start_u, start_d)
lb <- c(lb, min_u, d_lim[1])
ub <- c(ub, max_u, d_lim[2])
}
} else {
# periods is not null
gf <- internal_semipara(y, periods = periods, pgram = ss)
for (gf1 in gf) {
start_d <- gf1$fd
if (is.null(start_d)) start_d <- mean(d_lim)
if (is.na(start_d)) start_d <- mean(d_lim)
if (start_d >= d_lim[2]) start_d <- d_lim[2] - 0.01
if (start_d <= d_lim[1]) start_d <- d_lim[1] + 0.01
pars <- c(pars, start_d)
lb <- c(lb, d_lim[1])
ub <- c(ub, d_lim[2])
}
} # end of: if is null(periods) ... else ...
if (p + q > 0) {
# ARMA params - experiment suggests starting with 0 is as effective as any other method.
pars <- c(pars, rep(0, p + q))
lb <- c(lb, rep(-1, p + q))
ub <- c(ub, rep(1, p + q))
}
if (method == "WLL") {
# The WLL method needs to estimate the VAR along with other parameters.
pars <- c(pars, stats::sd(y))
lb <- c(lb, 1e-10)
ub <- c(ub, 2 * stats::var(y))
}
# create a list of all possible params any 'method' might need.
# The various objective functions can extract the parameters which are relevant to that method.
params <- list(
y = y,
ss = ss,
p = p,
q = q,
d = d,
k = k,
periods = periods,
method = method
)
message <- character(0)
# Optimisation functions for each method
fcns <- list(
"CSS" = internal_css_ggbr_obj,
"Whittle" = internal_whittle_garma_obj_short,
"WLL" = internal_wll_ggbr_obj
)
# apply a list of optimisation methods in sequence
# result will be a list of the "fit" results for each algorithm
opt_result <- internal_generic_optim_list(
opt_method_list = opt_method,
initial_pars = pars,
fcn = fcns[[method]],
lb = lb,
ub = ub,
params = params,
control = control
)
fit <- opt_result[[length(opt_result)]]
result$opt_result <-list(opt_result)
result$convergence <- fit$convergence
result$conv_message <- fit$message
hh <- fit$hessian
# sigma2
if (method == "WLL") {
# adjust sigma2 for theoretical bias...
# Note: digamma() function is part of base R.
sigma2 <- fit$par[length(fit$par)] <- fit$par[length(fit$par)] / (2 * pi) * exp(-digamma(1))
} else if (method == "CSS") {
sigma2 <- fit$value[length(fit$value)] / length(y)
} # Chung (1996)
else if (method == "Whittle") sigma2 <- internal_whittle_garma_obj_short(fit$par, params) # GHR 2001.
result$sigma2 <- sigma2
if (!is.null(lm1)) {
coef <- c(lm1$coefficients, fit$par)
se <- sqrt(diag(vcov(lm1)))
} else {
coef <- fit$par
se <- numeric(0)
}
se1 <- rep(NA_real_, length(fit$par)) # the se values for the GARMA part of the model - default
if (method == "WLL") {
vcov1 <- matrix(nrow = length(fit$par) - 1, ncol = length(fit$par) - 1) # default
# "-1" above since we don't want to include the se calc in the vcov matrix
} else {
vcov1 <- matrix(nrow = length(fit$par), ncol = length(fit$par))
}
if (method != "WLL" & !is.null(hh)) {
# Next, find the se's for coefficients
start <- 1
se1 <- numeric(0) # default to set this up in the right environment
# next check the hessian
h_inv_diag <- diag(inv_hessian <- pracma::pinv(hh))
if (method == "Whittle") {
# Next line from GHR (2001) thm 3.2
omega <- internal_whittle_garma_omega(fit$par, params)
vcov1 <- pracma::pinv(omega) / length(y)
se1 <- suppressWarnings(sqrt(diag(vcov1)))
}
if (method == "CSS") {
se1 <- suppressWarnings(sqrt(h_inv_diag * sigma2 * 2))
vcov1 <- inv_hessian * 2 * sigma2
}
}
se <- c(se, se1)
rm(se1)
# set up names - nm
nm <- character(0)
if (!is.null(xreg)) {
nm <- colnames(xreg)
}
if (is.null(periods)) {
if (k > 0) nm <- c(nm, as.vector(unlist(lapply(1:k, function(x) paste0(c("u", "fd"), x)))))
} else {
nm <- c(nm, paste0("fd", 1:length(periods)))
}
if (p > 0) nm <- c(nm, paste0("ar", 1:p))
if (q > 0) nm <- c(nm, paste0("ma", 1:q))
if (method == "WLL") {
nm <- c(nm, "Var")
}
coef <- matrix(data=c(coef, se), nrow = 2, byrow = TRUE)
colnames(coef) <- nm
rownames(coef) <- c("", "s.e.")
result$coef <- coef
result$var.coef <- vcov1
colnames(vcov1) <- rownames(vcov1) <- tail(nm, nrow(vcov1))
# build a ggbr_factors object
gf <- list()
if (is.null(xreg)) {
start_idx <- 1
} else {
start_idx <- ncol(xreg) + 1
}
if (is.null(periods)) {
for (k1 in seq_len(k)) {
u <- coef[1, start_idx]
gf1 <- list(freq = acos(u) / (2 * pi), period = (2 * pi) / acos(u), fd = coef[1, start_idx + 1])
gf <- c(gf, list(gf1))
start_idx <- start_idx + 2
}
} else {
for (period in periods) {
gf1 <- list(freq = 1/period, period = period, fd = coef[1, start_idx])
gf <- c(gf, list(gf1))
start_idx <- start_idx + 1
}
}
class(gf) <- "ggbr_factors"
model <- list(
"phi" = coef[1, substr(colnames(coef), 1, 2) == "ar"],
"theta" = coef[1, substr(colnames(coef), 1, 2) == "ma"],
"ggbr_factors" = gf
)
if (!is.null(xreg)) {
model$xreg = coef[1, 1:ncol(xreg)]
}
result$model <- model
# fitted values and residuals
result <- internal_fitted_values(result)
# log lik
result$loglik <-
(-length(y) / 2 * log(2 * pi)
- length(y) / 2 * log(sigma2)
- 1 / (2 * sigma2) * sum(result$residuals^2))
result$aic <- -2 * result$loglik + 2 * (p + q + ifelse(is.null(periods), 2*k, length(periods)) + 1)
class(result) <- "garma_model"
return(result)
}
# TO DO:
# 3. predict to include xreg factors
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.