R/nonparametric.R

Defines functions opt_lambda summary.midas_r_np print.midas_r_np BIC.midas_r_np AIC.midas_r_np midas_r_np

Documented in midas_r_np

##' Estimates non-parametric MIDAS regression
##'
##' Estimates non-parametric MIDAS regression accodring Breitung et al.
##'
##' @title Estimate non-parametric MIDAS regression
##' @param formula formula specifying MIDAS regression
##' @param data a named list containing data with mixed frequencies
##' @param lambda smoothing parameter, defaults to \code{NULL}, which means that it is chosen by minimising AIC.
##' @return a \code{midas_r_np} object
##' @author Vaidotas Zemlys
##' @references Breitung J, Roling C, Elengikal S (2013). \emph{Forecasting inflation rates using daily data: A nonparametric MIDAS approach} Working paper, URL http://www.ect.uni-bonn.de/mitarbeiter/joerg-breitung/npmidas.
##' @export
##' @import Matrix
##' @importFrom stats optimize
##' @examples
##' data("USunempr")
##' data("USrealgdp")
##' y <- diff(log(USrealgdp))
##' x <- window(diff(USunempr),start=1949)
##' trend <- 1:length(y)
##' midas_r_np(y~trend+fmls(x,12,12))
midas_r_np <- function(formula, data, lambda = NULL) {
  Zenv <- new.env(parent = environment(formula))

  if (missing(data)) {
    ee <- NULL
  }
  else {
    ee <- data_to_env(data)
    parent.env(ee) <- parent.frame()
  }

  assign("ee", ee, Zenv)
  formula <- as.formula(formula)
  cl <- match.call()
  mf <- match.call(expand.dots = FALSE)
  mf$formula <- formula
  m <- match(c("formula", "data"), names(mf), 0L)
  mf <- mf[c(1L, m)]
  mf[[1L]] <- as.name("model.frame")
  mf[[3L]] <- as.name("ee")
  mf[[4L]] <- as.name("na.omit")
  names(mf)[c(2, 3, 4)] <- c("formula", "data", "na.action")

  mf <- eval(mf, Zenv)
  mt <- attr(mf, "terms")

  terms.lhs <- as.list(attr(mt, "variables"))[-2:-1]
  term.labels <- attr(mt, "term.labels")

  rfd <- vector("list", length(terms.lhs))
  yname <- all.vars(mt[[2]])

  for (i in 1:length(rfd)) {
    fr <- terms.lhs[[i]]
    # This a behaviour of R we rely on. It might be non-standard one.
    fun <- as.character(fr)[1]
    rfd[[i]] <- if (fun %in% c("fmls", "mls", "dmls")) {
      lags <- eval(fr[[3]], Zenv)
      nm <- as.character(fr[[2]])
      nol <- switch(fun,
        fmls = lags + 1,
        dmls = lags + 1,
        mls = length(lags)
      )
      lagstruct <- switch(fun,
        fmls = 0:lags,
        dmls = 0:lags,
        mls = lags
      )

      if (nol < 3 & nm != yname) stop("For nonparametric MIDAS you need at least 3 high frequency lags")

      wlab <- ifelse(nm == yname, "", nm)
      list(
        weight = function(p) p,
        term_name = nm,
        gradient = NULL,
        start = NULL,
        weight_name = "non-parametric weight",
        frequency = eval(fr[[4]], Zenv),
        lag_structure = lagstruct
      )
    } else {
      list(
        weight = function(p) p,
        term_name = term.labels[i],
        gradient = NULL,
        start = NULL,
        weight_name = "",
        frequency = 1,
        lag_structure = 0
      )
    }
  }

  if (attr(mt, "intercept") == 1) {
    rfd <- c(list(list(
      weight = function(p) p,
      term_name = "(Intercept)",
      gradient = NULL,
      start = NULL,
      weight_name = "",
      frequency = 1,
      lag_structure = 0
    )), rfd)
  }


  names(rfd) <- sapply(rfd, "[[", "term_name")

  ## Note this is a bit of misnomer. Variable weight_names is actualy a vector of term names which have MIDAS weights.
  ## It *is not* the same as actual name of weight function. This is a left-over from the old code. Grabbed from prepmidas_r

  weight_names <- sapply(rfd, "[[", "weight_name")
  weight_inds <- which(weight_names != "")
  weight_names <- names(rfd)[weight_names != ""]

  lengths <- sapply(lapply(rfd, "[[", "lag_structure"), length)

  build_indices <- function(ci, nm) {
    inds <- cbind(c(1, ci[-length(ci)] + 1), ci)
    inds <- apply(inds, 1, function(x) list(x[1]:x[2]))
    inds <- lapply(inds, function(x) x[[1]])
    names(inds) <- nm
    inds
  }

  pinds <- build_indices(cumsum(lengths), names(rfd))

  if (length(weight_names) > 1) stop("Only one non-autoregressive mixed frequency term is currently supported")

  resplace <- pinds[[weight_names]][1]
  rno <- length(rfd[[weight_names]]$lag_structure)

  y <- model.response(mf, "numeric")
  X <- model.matrix(mt, mf)

  k <- ncol(X)
  if (k < nrow(X)) {
    unrestricted <- lm(y ~ . - 1, data = data.frame(cbind(y, X), check.names = FALSE))
  } else {
    unrestricted <- NULL
  }

  D <- bandSparse(rno - 2, k, resplace - 1 + c(0, 1, 2), diagonals = list(rep(1, rno - 2), rep(-2, rno - 2), rep(1, rno - 2)))

  DD <- crossprod(D)
  ol <- opt_lambda(y, X, DD, lambda)

  fit <- X %*% ol$beta
  res <- y - fit

  cf <- as.numeric(ol$beta)
  names(cf) <- names(unlist(pinds))

  term_info <- rfd
  names(term_info) <- sapply(term_info, "[[", "term_name")
  term_info <- mapply(function(term, pind, xind) {
    term$start <- NULL
    term$coef_index <- pind
    term$midas_coef_index <- pind
    term
  }, term_info, pinds[names(term_info)], SIMPLIFY = FALSE)


  out <- list(
    coefficients = cf,
    midas_coefficients = cf,
    model = cbind(y, X),
    unrestricted = unrestricted,
    call = cl,
    terms = mt,
    fitted.values = as.numeric(fit),
    residuals = as.numeric(res),
    term_info = term_info,
    lambda = ol$lambda,
    klambda = ol$klambda,
    AIC = ol$AIC,
    opt = ol$opt,
    Zenv = Zenv
  )
  class(out) <- "midas_r_np"
  out
}

##' @importFrom stats AIC
##' @export
##' @method AIC midas_r_np
AIC.midas_r_np <- function(object, ..., k) {
  object$AIC(object$lambda)
}

##' @importFrom stats BIC
##' @export
##' @method BIC midas_r_np
BIC.midas_r_np <- function(object, ..., k) {
  # for compatibility
  object$AIC(object$lambda)
}

##' @export
##' @method forecast midas_r_np
forecast.midas_r_np <- forecast.midas_r

##' @export
##' @method predict midas_r_np
predict.midas_r_np <- predict.midas_r

##' @export
##' @method print midas_r_np
print.midas_r_np <- function(x, ...) {
  cat("Nonparametric MIDAS regression model", paste0("(", nrow(x$model)), "low frequency observations)")
  cat("\nFormula: ", deparse(formula(terms(x))))
  cat("\nThe smoothing parameter: ", x$lambda)
  cat("\nThe effective number of parameters:", x$klambda)
  cat("\nAIC of the model: ", AIC(x))
  cat("\nRoot mean squared error: ", sqrt(mean(residuals(x)^2)), "\n")
}

##' @export
##' @method summary midas_r_np
summary.midas_r_np <- function(object, ...) {
  print(object, ...)
}

opt_lambda <- function(y, X, DD, lambda) {
  n <- length(y)
  XX <- crossprod(X)
  Xy <- crossprod(X, y)
  tX <- t(X)
  AIC <- function(lambda) {
    Qlambda <- XX + lambda * n * DD
    klambda <- sum(X * t(solve(Qlambda, tX)))
    beta <- solve(Qlambda, Xy)
    res <- y - X %*% beta
    log(sum(res^2)) + 2 * (klambda + 1) / (n - klambda - 2)
  }
  if (is.null(lambda)) {
    opt <- optimize(AIC, c(0, 100))
    lambda <- opt$minimum
  }
  else {
    opt <- NULL
  }
  Qlambda <- XX + lambda * n * DD
  klambda <- sum(X * t(solve(Qlambda, tX)))
  beta <- solve(Qlambda, Xy)
  AICl <- AIC(lambda)
  list(beta = beta, klambda = klambda, lambda = lambda, AIC = AIC, opt = opt)
}
mpiktas/midasr documentation built on Aug. 24, 2022, 2:32 p.m.