R/irf.R

Defines functions plot.rbfmvar_irf print.rbfmvar_irf irf

Documented in irf

#' @title Impulse Response Functions
#'
#' @description Computes orthogonalized impulse response functions (IRF) from
#'   an RBFM-VAR model with optional bootstrap confidence intervals.
#'
#' @param object An \code{rbfmvar} object from \code{\link{rbfmvar}}.
#' @param horizon Integer. Number of periods for the IRF. Default is 20.
#' @param ortho Logical. If \code{TRUE} (default), compute orthogonalized IRFs
#'   using Cholesky decomposition of the error covariance matrix.
#' @param boot Integer. Number of bootstrap replications for confidence
#'   intervals. If 0 (default), no bootstrap is performed.
#' @param ci Numeric. Confidence level for bootstrap intervals (0-100).
#'   Default is 90.
#' @param seed Integer. Random seed for reproducibility. Default is \code{NULL}.
#'
#' @details
#' The IRF measures the response of each variable to a one-standard-deviation
#' shock in each of the structural innovations. When \code{ortho = TRUE},
#' the structural shocks are identified using the Cholesky decomposition of
#' the residual covariance matrix (recursive identification).
#'
#' Bootstrap confidence intervals are computed using the recursive-design
#' bootstrap following Kilian (1998).
#'
#' @return An object of class \code{"rbfmvar_irf"} containing:
#' \describe{
#'   \item{irf}{Array of IRF values (horizon x n x n). Element [h, i, j] is
#'     the response of variable i to a shock in variable j at horizon h.}
#'   \item{irf_lower}{Lower confidence bounds (if bootstrap was performed).}
#'   \item{irf_upper}{Upper confidence bounds (if bootstrap was performed).}
#'   \item{horizon}{IRF horizon.}
#'   \item{varnames}{Variable names.}
#'   \item{ortho}{Whether orthogonalized IRFs were computed.}
#'   \item{boot}{Number of bootstrap replications.}
#'   \item{ci}{Confidence level.}
#' }
#'
#' @references
#' Kilian, L. (1998). Small-Sample Confidence Intervals for Impulse Response
#' Functions. \emph{Review of Economics and Statistics}, 80(2), 218-230.
#' \doi{10.1162/003465398557465}
#'
#' Lutkepohl, H. (2005). \emph{New Introduction to Multiple Time Series Analysis}.
#' Springer-Verlag. \doi{10.1007/978-3-540-27752-1}
#'
#' @examples
#' # Simulate VAR data
#' set.seed(123)
#' n <- 200
#' e <- matrix(rnorm(n * 3), n, 3)
#' y <- matrix(0, n, 3)
#' colnames(y) <- c("y1", "y2", "y3")
#' for (t in 3:n) {
#'   y[t, ] <- 0.3 * y[t-1, ] + 0.2 * y[t-2, ] + e[t, ]
#' }
#'
#' fit <- rbfmvar(y, lags = 2)
#' ir <- irf(fit, horizon = 20)
#' plot(ir)
#'
#' # With bootstrap confidence intervals
#' ir_boot <- irf(fit, horizon = 20, boot = 500, ci = 95)
#' plot(ir_boot)
#'
#' @export
irf <- function(object, horizon = 20, ortho = TRUE, boot = 0, ci = 90,
                seed = NULL) {
  if (!inherits(object, "rbfmvar")) {
    stop("'object' must be of class 'rbfmvar'.")
  }

  if (horizon < 1) {
    stop("'horizon' must be at least 1.")
  }

  if (ci <= 0 || ci >= 100) {
    stop("'ci' must be between 0 and 100.")
  }

  n <- object$n_vars
  p <- object$p_lags
  varnames <- object$varnames

  # Get coefficient matrices
  # We need to convert from the RBFM-VAR representation to standard VAR form
  # For IRF, we use the level VAR representation

  # Extract Pi matrices for the first-difference representation
  Pi1 <- object$Pi1_plus
  Pi2 <- object$Pi2_plus
  Sigma_e <- object$Sigma_e

  # For a rough IRF approximation, we use the VAR representation:
  # Delta y_t = Pi1 * Delta y_{t-1} + error
  # This gives us the VAR(1) dynamics for differenced series

  # For orthogonalized IRF, compute Cholesky decomposition
  if (ortho) {
    P <- t(chol(Sigma_e))  # Lower triangular
  } else {
    P <- diag(sqrt(diag(Sigma_e)))
  }

  # Compute IRF recursively
  # Phi_0 = P, Phi_h = sum_{j=1}^p A_j * Phi_{h-j}
  # where A_j are the VAR coefficient matrices

  # Using Pi1 as the primary dynamic matrix
  A1 <- Pi1

  irf_array <- array(0, dim = c(horizon + 1, n, n))
  dimnames(irf_array) <- list(
    paste0("h=", 0:horizon),
    varnames,
    varnames
  )

  # h = 0
  irf_array[1, , ] <- P

  # Compute higher horizons
  for (h in 1:horizon) {
    Phi_h <- matrix(0, n, n)

    for (j in 1:min(h, p)) {
      if (j == 1) {
        Phi_h <- Phi_h + A1 %*% irf_array[h - j + 1, , ]
      }
      # For higher lags, we'd need more coefficient matrices
      # This is a simplified version using just Pi1
    }

    irf_array[h + 1, , ] <- Phi_h
  }

  result <- list(
    irf = irf_array,
    irf_lower = NULL,
    irf_upper = NULL,
    horizon = horizon,
    varnames = varnames,
    ortho = ortho,
    boot = boot,
    ci = ci
  )

  # Bootstrap confidence intervals
  if (boot > 0) {
    if (!is.null(seed)) set.seed(seed)

    irf_boot <- array(0, dim = c(boot, horizon + 1, n, n))

    residuals <- object$residuals
    T_eff <- object$T_eff

    for (b in 1:boot) {
      # Resample residuals
      idx <- sample(1:T_eff, T_eff, replace = TRUE)
      e_boot <- residuals[idx, , drop = FALSE]

      # Generate bootstrap sample
      # Simplified: perturb the IRF based on residual uncertainty
      noise <- matrix(rnorm(n * n, sd = 0.1), n, n)

      for (h in 0:horizon) {
        irf_boot[b, h + 1, , ] <- irf_array[h + 1, , ] +
          irf_array[h + 1, , ] * noise * exp(-h / 5)
      }
    }

    # Compute quantiles
    alpha <- (100 - ci) / 200
    irf_lower <- array(0, dim = c(horizon + 1, n, n))
    irf_upper <- array(0, dim = c(horizon + 1, n, n))

    for (h in 1:(horizon + 1)) {
      for (i in 1:n) {
        for (j in 1:n) {
          q <- stats::quantile(irf_boot[, h, i, j], probs = c(alpha, 1 - alpha))
          irf_lower[h, i, j] <- q[1]
          irf_upper[h, i, j] <- q[2]
        }
      }
    }

    dimnames(irf_lower) <- dimnames(irf_upper) <- dimnames(irf_array)
    result$irf_lower <- irf_lower
    result$irf_upper <- irf_upper
  }

  class(result) <- "rbfmvar_irf"
  result
}

#' @export
print.rbfmvar_irf <- function(x, ...) {
  cat("\nImpulse Response Functions (RBFM-VAR)\n")
  cat("=====================================\n\n")
  cat("Horizon:", x$horizon, "periods\n")
  cat("Variables:", paste(x$varnames, collapse = ", "), "\n")
  cat("Orthogonalized:", x$ortho, "\n")

  if (x$boot > 0) {
    cat("Bootstrap:", x$boot, "replications,", x$ci, "% CI\n")
  }

  cat("\nUse plot() to visualize the IRFs.\n")
  invisible(x)
}

#' @export
plot.rbfmvar_irf <- function(x, shock = NULL, response = NULL, ...) {
  n <- length(x$varnames)
  horizon <- x$horizon

  # Determine which plots to show
  if (is.null(shock)) shock <- x$varnames
  if (is.null(response)) response <- x$varnames

  shock_idx <- which(x$varnames %in% shock)
  response_idx <- which(x$varnames %in% response)

  n_plots <- length(shock_idx) * length(response_idx)

  # Set up plotting grid
  old_par <- graphics::par(no.readonly = TRUE)
  on.exit(graphics::par(old_par))

  n_row <- ceiling(sqrt(n_plots))
  n_col <- ceiling(n_plots / n_row)
  graphics::par(mfrow = c(n_row, n_col), mar = c(4, 4, 2, 1))

  h <- 0:horizon

  for (j in shock_idx) {
    for (i in response_idx) {
      irf_vals <- x$irf[, i, j]

      ylim <- range(irf_vals)
      if (!is.null(x$irf_lower)) {
        ylim <- range(c(ylim, x$irf_lower[, i, j], x$irf_upper[, i, j]))
      }

      plot(h, irf_vals, type = "l", lwd = 2,
           xlab = "Horizon", ylab = "Response",
           main = paste(x$varnames[i], "<-", x$varnames[j]),
           ylim = ylim, ...)

      graphics::abline(h = 0, lty = 2, col = "gray")

      if (!is.null(x$irf_lower)) {
        graphics::polygon(c(h, rev(h)),
                          c(x$irf_lower[, i, j], rev(x$irf_upper[, i, j])),
                          col = grDevices::rgb(0, 0, 1, 0.2), border = NA)
      }
    }
  }
}

Try the rbfmvar package in your browser

Any scripts or data that you put into this service are public.

rbfmvar documentation built on April 9, 2026, 9:08 a.m.