R/update.R

Defines functions update.fmforecast2 update.fmforecast

Documented in update.fmforecast update.fmforecast2

#' Updating functional demographic models and coherent functional demographic models.
#'
#' \code{update.fmforecast()} updates \code{fdm} forecasts. The argument \code{object} is the output from \code{\link{forecast.fdm}} which has been subsequently modified with new coefficient forecasts. These new forecasts are used when re-calculating the forecast of the mortality or fertility rates, or net migration numbers.
#' \code{update.fmforecast2()} updates \code{fdmpr} forecasts. The argument \code{object} is the output from \code{\link{forecast.fdmpr}} which has been subsequently modified with new coefficient forecasts.
#'
#' @param object Output from either \code{\link{fdm}} or \code{\link{coherentfdm}}.
#' @param ... Extra arguments currently ignored.
#'
#' @return A list of the same class as \code{object}.
#' @author Rob J Hyndman.
#' @seealso \code{\link{forecast.fdm}}, \code{\link{forecast.fdmpr}}
#' @examples
#' \dontrun{
#' france.fit <- fdm(fr.mort, order = 2)
#' france.fcast <- forecast(france.fit, 50)
#' # Replace first coefficient model with ARIMA(0,1,2)+drift
#' france.fcast$coeff[[2]] <- forecast(Arima(france.fit$coeff[, 2],
#'   order = c(0, 1, 2), include.drift = TRUE
#' ), h = 50, level = 80)
#' france.fcast <- update(france.fcast)
#'
#' fr.short <- extract.years(fr.sm, 1950:2006)
#' fr.fit <- coherentfdm(fr.short)
#' fr.fcast <- forecast(fr.fit)
#' par(mfrow = c(1, 2))
#' plot(fr.fcast$male)
#' # Replace first coefficient model in product component with a damped ETS model:
#' fr.fcast$product$coeff[[2]] <- forecast(ets(fr.fit$product$coeff[, 2], damped = TRUE),
#'   h = 50, level = 80
#' )
#' fr.fcast <- update(fr.fcast)
#' plot(fr.fcast$male)
#' }
#' @keywords models
#' @name update
NULL

# Update fmforecast object
# Original object from forecast.fdm
# Assumed that the coefficient forecasts have been subsequently changed
# Object needs to be updated to reflect those changes.
#' @rdname update
#' @export
update.fmforecast <- function(object, ...) {
  if (!is.element("fmforecast", class(object))) {
    stop("object must be of class fmforecast")
  }
  h <- length(object$year)
  nb <- ncol(object$model$basis)
  adjust <- length(object$var$adj.factor) > 1
  objnames <- dimnames(object$rate[[1]])

  # Update in-sample fitted values and errors.
  fitted <- matrix(NA, length(object$model$year), nb)
  meanfcast <- varfcast <- matrix(NA, nrow = h, ncol = nb)
  qconf <- stats::qnorm(0.5 + object$coeff[[2]]$level[1] / 200)
  fitted[, 1] <- 1
  meanfcast[, 1] <- 1
  varfcast[, 1] <- 0
  for (i in 2:nb)
  {
    fitted[, i] <- fitted(object$coeff[[i]]$model)
    meanfcast[, i] <- object$coeff[[i]]$mean
    varfcast[, i] <- ((object$coeff[[i]]$upper - object$coeff[[i]]$lower) / (2 * qconf))^2
  }
  object$fitted$y <- object$model$basis %*% t(fitted)
  object$error$y <- object$model$y$y - object$fitted$y
  object$coeff.error <- object$model$coeff - fitted

  # Update point forecasts
  object$rate[[1]] <- object$model$basis %*% t(meanfcast)
  dimnames(object$rate[[1]]) <- objnames

  # Update forecast variances
  # Only model variance should have changed
  modelvar <- object$model$basis^2 %*% t(varfcast)
  totalvar <- sweep(modelvar, 1, object$var$error + object$var$mean, "+")
  if (adjust & nb > 1) {
    object$var$adj.factor <- rowMeans(object$error$y^2, na.rm = TRUE) / totalvar[, 1]
    totalvar <- sweep(totalvar, 1, object$var$adj.factor, "*")
  }
  # Add observational variance to total variance
  object$var$total <- sweep(totalvar, 1, object$var$observ, "+")

  # Update forecast intervals
  # Only parametric intervals computed here.
  tmp <- qconf * sqrt(object$var$total)
  object$rate$lower <- InvBoxCox(object$rate[[1]] - tmp, object$lambda)
  object$rate$upper <- InvBoxCox(object$rate[[1]] + tmp, object$lambda)
  object$rate[[1]] <- InvBoxCox(object$rate[[1]], object$lambda)
  if (object$type != "migration") {
    object$rate[[1]] <- pmax(object$rate[[1]], 0.000000001)
    object$rate$lower <- pmax(object$rate$lower, 0.000000001)
    object$rate$lower[is.na(object$rate$lower)] <- 0
    object$rate$upper <- pmax(object$rate$upper, 0.000000001)
  }

  # Return updated object
  return(object)
}


# Function to combine product and ratio forecasts
# object is output from forecast.fdmpr, but with modified forecasts
# This function simply recombines them again.
#' @rdname update
#' @export
update.fmforecast2 <- function(object, ...) {
  if (!is.element("fmforecast2", class(object))) {
    stop("object must be of class fmforecast2")
  }

  J <- length(object$ratio)
  ny <- length(object$ratio[[1]]$year)

  # GM model
  object$product <- update(object$product)

  # Obtain forecasts for each group
  is.mortality <- (object$product$type == "mortality")
  y <- as.numeric(is.mortality) # =1 for mortality and 0 for migration
  for (j in 1:J)
  {
    object$ratio[[j]] <- update(object$ratio[[j]])
    if (is.mortality) {
      object[[j]]$rate[[1]] <- object$product$rate$product * object$ratio[[j]]$rate[[1]]
    } else {
      object[[j]]$rate[[1]] <- object$product$rate$product + object$ratio[[j]]$rate[[1]]
    }
    if (is.mortality) {
      y <- y * object[[j]]$rate[[1]]
    } else {
      y <- y + object[[j]]$rate[[1]]
    }
  }

  # Adjust forecasts so they multiply appropriately.
  if (is.mortality) {
    y <- y^(1 / J) / object$product$rate$product
    for (j in 1:J) {
      object[[j]]$rate[[1]] <- object[[j]]$rate[[1]] / y
    }
  } else {
    y <- y / J - object$product$rate$product
    for (j in 1:J) {
      object[[j]]$rate[[1]] <- object[[j]]$rate[[1]] - y
    }
  }
  # Variance of forecasts
  qconf <- 2 * stats::qnorm(0.5 + object$product$coeff[[1]]$level / 200)
  for (j in 1:J)
  {
    vartotal <- object$product$var$total + object$ratio[[j]]$var$total
    tmp <- qconf * sqrt(vartotal)
    object[[j]]$rate$lower <- InvBoxCox(BoxCox(object[[j]]$rate[[1]], object$product$lambda) - tmp, object$product$lambda)
    object[[j]]$rate$upper <- InvBoxCox(BoxCox(object[[j]]$rate[[1]], object$product$lambda) + tmp, object$product$lambda)
  }

  return(object)
}
robjhyndman/demography documentation built on Dec. 7, 2024, 11:18 p.m.