| # File src/library/stats/R/arma0.R |
| # Part of the R package, https://www.R-project.org |
| # |
| # Copyright (C) 1999-2012 The R Core Team |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # A copy of the GNU General Public License is available at |
| # https://www.R-project.org/Licenses/ |
| |
| arima0 <- function(x, order = c(0, 0, 0), |
| seasonal = list(order = c(0, 0, 0), period = NA), |
| xreg = NULL, include.mean = TRUE, delta = 0.01, |
| transform.pars = TRUE, fixed = NULL, init = NULL, |
| method = c("ML", "CSS"), n.cond, |
| optim.control = list()) |
| { |
| arma0f <- function(p) |
| { |
| par <- as.double(fixed) |
| par[mask] <- p |
| .Call(C_arma0fa, G, par) |
| } |
| |
| arCheck <- function(ar) |
| { |
| p <- max(which(c(1, -ar) != 0)) - 1 |
| if(!p) return(TRUE) |
| all(Mod(polyroot(c(1, -ar[1L:p]))) > 1) |
| } |
| |
| maInvert <- function(ma) |
| { |
| ## polyroot can't cope with leading zero. |
| q <- length(ma) |
| q0 <- max(which(c(1,ma) != 0)) - 1 |
| if(!q0) return(ma) |
| roots <- polyroot(c(1, ma[1L:q0])) |
| ind <- Mod(roots) < 1 |
| if(all(!ind)) return(ma) |
| warning("converting non-invertible initial MA values") |
| if(q0 == 1) return(c(1/ma[1L], rep(0, q-q0))) |
| roots[ind] <- 1/roots[ind] |
| x <- 1 |
| for(r in roots) x <- c(x, 0) - c(0, x)/r |
| c(Re(x[-1L]), rep(0, q-q0)) |
| } |
| |
| series <- deparse(substitute(x)) |
| if(NCOL(x) > 1) |
| stop("only implemented for univariate time series") |
| method <- match.arg(method) |
| x <- as.ts(x) |
| if(!is.numeric(x)) |
| stop("'x' must be numeric") |
| dim(x) <- NULL |
| n <- length(x) |
| |
| if(!missing(order)) |
| if(!is.numeric(order) || length(order) != 3L || any(order < 0)) |
| stop("'order' must be a non-negative numeric vector of length 3") |
| if(!missing(seasonal)) |
| if(is.list(seasonal)) { |
| if(is.null(seasonal$order)) |
| stop("'seasonal' must be a list with component 'order'") |
| if(!is.numeric(seasonal$order) || length(seasonal$order) != 3L |
| || any(seasonal$order < 0)) |
| stop("'seasonal$order' must be a non-negative numeric vector of length 3") |
| } else if(is.numeric(order)) { |
| if(length(order) == 3) seasonal <- list(order=seasonal) |
| else ("'seasonal' is of the wrong length") |
| } else stop("'seasonal' must be a list with component 'order'") |
| |
| if(is.null(seasonal$period) || is.na(seasonal$period) |
| || seasonal$period == 0) seasonal$period <- frequency(x) |
| arma <- c(order[-2L], seasonal$order[-2L], seasonal$period, |
| order[2L], seasonal$order[2L]) |
| narma <- sum(arma[1L:4L]) |
| if(d <- order[2L]) x <- diff(x, 1, d) |
| if(d <- seasonal$order[2L]) x <- diff(x, seasonal$period, d) |
| xtsp <- tsp(x) |
| tsp(x) <- NULL |
| nd <- order[2L] + seasonal$order[2L] |
| n.used <- length(x) |
| ncond <- n - n.used |
| if(method == "CSS") { |
| ncond1 <- order[1L] + seasonal$period * seasonal$order[1L] |
| ncond <- if(!missing(n.cond)) ncond + max(n.cond, ncond1) |
| else ncond + ncond1 |
| } |
| if(is.null(xreg)) { |
| ncxreg <- 0 |
| } else { |
| if(NROW(xreg) != n) stop("lengths of 'x' and 'xreg' do not match") |
| ncxreg <- NCOL(xreg) |
| } |
| class(xreg) <- NULL |
| if(include.mean && (nd == 0)) { |
| if(is.matrix(xreg) && is.null(colnames(xreg))) |
| colnames(xreg) <- paste0("xreg", 1L:ncxreg) |
| xreg <- cbind(intercept = rep_len(1, n), xreg = xreg) |
| ncxreg <- ncxreg + 1 |
| } |
| |
| if (is.null(fixed)) fixed <- rep_len(NA_real_, narma + ncxreg) |
| else if(length(fixed) != narma + ncxreg) stop("wrong length for 'fixed'") |
| mask <- is.na(fixed) |
| if(!any(mask)) stop("all parameters were fixed") |
| if(transform.pars && any(!mask[1L:narma])) { |
| warning("some ARMA parameters were fixed: setting transform.pars = FALSE") |
| transform.pars <- FALSE |
| } |
| |
| if(ncxreg) { |
| if(d <- order[2L]) xreg <- diff(xreg, 1, d) |
| if(d <- seasonal$order[2L]) xreg <- diff(xreg, seasonal$period, d) |
| xreg <- as.matrix(xreg) |
| if(qr(na.omit(xreg))$rank < ncol(xreg)) stop("'xreg' is collinear") |
| if(is.null(cn <- colnames(xreg))) |
| cn <- paste0("xreg", 1L:ncxreg) |
| } |
| if(anyNA(x) || (ncxreg && anyNA(xreg))) |
| ## only exact recursions handle NAs |
| if(method == "ML" && delta >= 0) { |
| warning("NAs present: setting 'delta' to -1") |
| delta <- -1 |
| } |
| |
| init0 <- rep_len(0, narma) |
| parscale <- rep_len(1, narma) |
| if (ncxreg) { |
| orig.xreg <- (ncxreg == 1) || any(!mask[narma + 1L:ncxreg]) |
| if(!orig.xreg) { |
| S <- svd(na.omit(xreg)) |
| xreg <- xreg %*% S$v |
| } |
| fit <- lm(x ~ xreg - 1, na.action = na.omit) |
| init0 <- c(init0, coef(fit)) |
| ses <- summary(fit)$coefficients[,2] |
| parscale <- c(parscale, ses) |
| } |
| |
| storage.mode(x) <- storage.mode(xreg) <- "double" |
| if(method == "CSS") transform.pars <- 0 |
| G <- .Call(C_setup_starma, as.integer(arma), x, n.used, xreg, |
| ncxreg, delta, transform.pars > 0, |
| ncond - (n - n.used)) |
| on.exit(.Call(C_free_starma, G)) |
| |
| if(!is.null(init)) { |
| if(length(init) != length(init0)) |
| stop("'init' is of the wrong length") |
| if(any(ind <- is.na(init))) init[ind] <- init0[ind] |
| if(transform.pars) { |
| if(any(!mask[1L:narma])) |
| warning("transformed ARMA parameters were fixed") |
| ## check stationarity |
| if(arma[1L] > 0) |
| if(!arCheck(init[1L:arma[1L]])) |
| stop("non-stationary AR part") |
| if(arma[3L] > 0) |
| if(!arCheck(init[sum(arma[1L:2]) + 1L:arma[3L]])) |
| stop("non-stationary seasonal AR part") |
| ## enforce invertibility |
| if(arma[2L] > 0) { |
| ind <- arma[1L] + 1L:arma[2L] |
| init[ind] <- maInvert(init[ind]) |
| } |
| if(arma[4L] > 0) { |
| ind <- sum(arma[1L:3]) + 1L:arma[4L] |
| init[ind] <- maInvert(init[ind]) |
| } |
| init <- .Call(C_Invtrans, G, as.double(init)) |
| } |
| } else init <- init0 |
| |
| |
| .Call(C_Starma_method, G, method == "CSS") |
| if(!("parscale" %in% names(optim.control))) |
| optim.control$parscale <- parscale[mask] |
| res <- optim(init[mask], arma0f, method = "BFGS", |
| hessian = TRUE, control = optim.control) |
| if((code <- res$convergence) > 0) |
| warning(gettextf("possible convergence problem: optim gave code = %d", |
| code), domain = NA) |
| coef <- res$par |
| |
| if(transform.pars) { |
| cf <- fixed |
| cf[mask] <- coef |
| ## do it this way to ensure hessian was computed inside |
| ## stationarity region |
| A <- .Call(C_Gradtrans, G, as.double(cf))[mask, mask] |
| var <- t(A) %*% solve(res$hessian*length(x)) %*% A |
| coef <- .Call(C_Dotrans, G, as.double(cf))[mask] |
| .Call(C_set_trans, G, 0) |
| } else var <- solve(res$hessian*length(x)) |
| arma0f(coef) # reset pars |
| sigma2 <- .Call(C_get_s2, G) |
| resid <- .Call(C_get_resid, G) |
| tsp(resid) <- xtsp |
| class(resid) <- "ts" |
| n.used <- sum(!is.na(resid)) |
| nm <- NULL |
| if(arma[1L] > 0) nm <- c(nm, paste0("ar", 1L:arma[1L])) |
| if(arma[2L] > 0) nm <- c(nm, paste0("ma", 1L:arma[2L])) |
| if(arma[3L] > 0) nm <- c(nm, paste0("sar", 1L:arma[3L])) |
| if(arma[4L] > 0) nm <- c(nm, paste0("sma", 1L:arma[4L])) |
| fixed[mask] <- coef |
| if(ncxreg > 0) { |
| nm <- c(nm, cn) |
| if(!orig.xreg) { |
| ind <- narma + 1L:ncxreg |
| fixed[ind] <- S$v %*% fixed[ind] |
| A <- diag(narma + ncxreg) |
| A[ind, ind] <- S$v |
| A <- A[mask, mask] |
| var <- A %*% var %*% t(A) |
| } |
| } |
| names(fixed) <- nm |
| names(arma) <- c("ar", "ma", "sar", "sma", "period", "diff", "sdiff") |
| dimnames(var) <- list(nm[mask], nm[mask]) |
| value <- 2 * n.used * res$value + n.used + n.used*log(2*pi) |
| aic <- if(method != "CSS") value + 2*length(coef) + 2 else NA |
| res <- list(coef = fixed, sigma2 = sigma2, var.coef = var, mask = mask, |
| loglik = -0.5*value, aic = aic, arma = arma, |
| residuals = resid, |
| call = match.call(), series = series, |
| code = code, n.cond = ncond) |
| class(res) <- "arima0" |
| res |
| } |
| |
| print.arima0 <- function(x, digits = max(3L, getOption("digits") - 3L), |
| se = TRUE, ...) |
| { |
| cat("\nCall:", deparse(x$call, width.cutoff = 75L), "", sep = "\n") |
| cat("Coefficients:\n") |
| coef <- round(x$coef, digits = digits) |
| if (se && nrow(x$var.coef)) { |
| ses <- rep_len(0, length(coef)) |
| ses[x$mask] <- round(sqrt(diag(x$var.coef)), digits = digits) |
| coef <- matrix(coef, 1, dimnames = list(NULL, names(coef))) |
| coef <- rbind(coef, s.e. = ses) |
| } |
| print.default(coef, print.gap = 2) |
| cm <- x$call$method |
| if(is.null(cm) || cm != "CSS") |
| cat("\nsigma^2 estimated as ", |
| format(x$sigma2, digits = digits), |
| ": log likelihood = ", format(round(x$loglik,2)), |
| ", aic = ", format(round(x$aic,2)), |
| "\n", sep = "") |
| else |
| cat("\nsigma^2 estimated as ", |
| format(x$sigma2, digits = digits), |
| ": part log likelihood = ", format(round(x$loglik,2)), |
| "\n", sep = "") |
| invisible(x) |
| } |
| |
| predict.arima0 <- |
| function(object, n.ahead = 1L, newxreg = NULL, se.fit=TRUE, ...) |
| { |
| myNCOL <- function(x) if(is.null(x)) 0 else NCOL(x) |
| data <- eval.parent(parse(text = object$series)) |
| xr <- object$call$xreg |
| xreg <- if(!is.null(xr)) eval.parent(xr) else NULL |
| ncxreg <- myNCOL(xreg) |
| if(myNCOL(newxreg) != ncxreg) |
| stop("'xreg' and 'newxreg' have different numbers of columns") |
| class(xreg) <- NULL |
| xtsp <- tsp(object$residuals) |
| n <- length(data) |
| arma <- object$arma |
| coefs <- object$coef |
| narma <- sum(arma[1L:4L]) |
| if(length(coefs) > narma) { |
| if(names(coefs)[narma+1] == "intercept") { |
| xreg <- cbind(intercept = rep_len(1, n), xreg) |
| newxreg <- cbind(intercept = rep_len(1, n.ahead), newxreg) |
| ncxreg <- ncxreg+1 |
| } |
| data <- data - as.matrix(xreg) %*% coefs[-(1L:narma)] |
| xm <- drop(as.matrix(newxreg) %*% coefs[-(1L:narma)]) |
| } else xm <- 0 |
| ## check invertibility of MA part(s) |
| if(arma[2L] > 0) { |
| ma <- coefs[arma[1L] + 1L:arma[2L]] |
| if(any(Mod(polyroot(c(1, ma))) < 1)) |
| warning("MA part of model is not invertible") |
| } |
| if(arma[4L] > 0) { |
| ma <- coefs[sum(arma[1L:3L]) + 1L:arma[4L]] |
| if(any(Mod(polyroot(c(1, ma))) < 1)) |
| warning("seasonal MA part of model is not invertible") |
| } |
| storage.mode(data) <- "double" |
| G <- .Call(C_setup_starma, as.integer(arma), data, n, rep_len(0., n), |
| 0., -1., 0., 0.) |
| on.exit(.Call(C_free_starma, G)) |
| .Call(C_Starma_method, G, TRUE) |
| .Call(C_arma0fa, G, as.double(coefs)) |
| z <- .Call(C_arma0_kfore, G, arma[6L], arma[7L], n.ahead) |
| pred <- ts(z[[1L]] + xm, start = xtsp[2L] + deltat(data), |
| frequency = xtsp[3L]) |
| if(se.fit) { |
| se <- ts(sqrt(z[[2L]]), |
| start = xtsp[2L] + deltat(data), frequency = xtsp[3L]) |
| return(list(pred=pred, se=se)) |
| } else return(pred) |
| } |
| |
| arima0.diag <- function(...) .Defunct() |
| |
| tsdiag.Arima <- tsdiag.arima0 <- function(object, gof.lag = 10, ...) |
| { |
| ## plot standardized residuals, acf of residuals, Ljung-Box p-values |
| oldpar <- par(mfrow = c(3, 1)) |
| on.exit(par(oldpar)) |
| rs <- object$residuals |
| stdres <- rs/sqrt(object$sigma2) |
| plot(stdres, type = "h", main = "Standardized Residuals", ylab = "") |
| abline(h = 0) |
| acf(object$residuals, plot = TRUE, main = "ACF of Residuals", |
| na.action = na.pass) |
| nlag <- gof.lag |
| pval <- numeric(nlag) |
| for(i in 1L:nlag) pval[i] <- Box.test(rs, i, type="Ljung-Box")$p.value |
| plot(1L:nlag, pval, xlab = "lag", ylab = "p value", ylim = c(0,1), |
| main = "p values for Ljung-Box statistic") |
| abline(h = 0.05, lty = 2, col = "blue") |
| } |
| |
| tsdiag <- function(object, gof.lag, ...) UseMethod("tsdiag") |