R/derivatives.R

Defines functions dsd_dvarStruct.varConstPower dsd_dConstPower2 dsd_dConstPower1 dsd_dvarStruct.varPower dsd_dvarPower dsd_dvarStruct.varExp dsd_dvarExp dsd_dvarStruct.varIdent dsd_dvarStruct.default dsd_dvarStruct dV_dvarStruct sdRds dV_dsigmasq dR_dcorStruct.corSymm dR_dcor_index replace dR_dcorStruct.corCompSymm dR_dcorStruct.corARMA dR_dcorStruct.corMA1 dR_dcorMA1 dR_dcorStruct.corCAR1 dR_dcorStruct.corAR1 dR_dcorStruct.default dR_dcorStruct dV_dcorStruct dV_dreStruct dV_dTau_unstruct dV_dTau_index build_dV_list.lme build_dV_list.gls build_dV_list.default build_dV_list

#------------------------------------------------------------------------------
# Build list of derivative matrices wrt model parameters
#------------------------------------------------------------------------------

build_dV_list <- function(mod) UseMethod("build_dV_list")

build_dV_list.default <- function(mod) {
  mod_class <- paste(class(mod), collapse = "-")
  stop(paste0("Derivatives not available for models of class ", mod_class, "."))

}

build_dV_list.gls <- function(mod) {
  cor_params <- dV_dcorStruct(mod)                          # correlation structure
  var_params <- dV_dvarStruct(mod)                          # variance structure
  sigma_sq <- dV_dsigmasq(mod)                              # sigma_sq

  # Create a list of derivative matrices
  c(cor_params, var_params, sigma_sq)
}

build_dV_list.lme <- function(mod) {
  Tau_params <- dV_dreStruct(mod)                           # random effects structure(s)
  cor_params <- dV_dcorStruct(mod)                          # correlation structure
  var_params <- dV_dvarStruct(mod)                          # variance structure
  sigma_sq <- dV_dsigmasq(mod)                              # sigma_sq

  # Create a list of derivative matrices
  c(unlist(Tau_params, recursive = FALSE), cor_params, var_params, sigma_sq)
}



#------------------------------------------------------------------------------
# First derivative matrices wrt unstructured random effects covariances
#------------------------------------------------------------------------------

dV_dTau_index <- function(tau_index, Z_blocks, block) {
  dV_dTau <- lapply(Z_blocks, function(Z) Z[,tau_index, drop=FALSE] %*% t(Z)[rev(tau_index),,drop=FALSE])
  attr(dV_dTau, "groups") <- block
  return(dV_dTau)
}

dV_dTau_unstruct <- function(block, pdMat_class, Z_design) {
  Tau_q <- dim(Z_design)[2]
  Z_blocks <- by(Z_design, block, as.matrix, simplify = FALSE)

  if ("pdDiag" %in% pdMat_class) {
    tau_index <- cbind(seq(1,Tau_q), seq(1,Tau_q))
  } else if ("pdSymm" %in% pdMat_class) {
    tau_index <- cbind(unlist(sapply(1:Tau_q, function(x) seq(1,x))),
                       unlist(sapply(1:Tau_q, function(x) rep(x,x))))
  } else {
    stop("Tau_index only available for pdMat structures of class pdDiag and pdSymm.")
  }

  apply(tau_index, 1, function(t) dV_dTau_index(unique(t), Z_blocks = Z_blocks, block = block))
}

dV_dreStruct <- function(mod) {

  blocks <- mod$groups
  blocks_names <- names(blocks)

  if (!all(blocks_names %in%  names(mod$modelStruct$reStruct))) {
    names(mod$modelStruct$reStruct) <- rev(blocks_names)
  }

  b <- lapply(blocks_names, function(x) class(mod$modelStruct$reStruct[[x]])) # pdClass

  data <- nlme::getData(mod)
  Z_design <- model.matrix(mod$modelStruct$reStruct, data = data)
  if (inherits(mod$na.action, "exclude")) {
    Z_design <- Z_design[!(rownames(Z_design) %in% names(mod$na.action)),,drop=FALSE]
  }
  Z_names <- get_RE_names(mod$modelStruct$reStruct)[blocks_names]

  if (length(blocks) == 1L) {
    Z_list <- list(Z_design)
  } else {
    Z_list <- sapply(Z_names, function(x) Z_design[,x, drop = FALSE],
                     simplify = FALSE, USE.NAMES = TRUE)
  }

  mapply(dV_dTau_unstruct,
         block = blocks, pdMat_class = b, Z_design = Z_list,
         SIMPLIFY = FALSE)

}

#------------------------------------------------------------------------------
# First derivative matrices wrt correlation structures
#------------------------------------------------------------------------------

# Derivative of Sigma with respect to correlation structure

dV_dcorStruct <- function(mod) {

  # No derivatives if there's no correlation structure
  if (is.null(mod$modelStruct$corStruct)) return(NULL)

  dR_dcor <- dR_dcorStruct(mod$modelStruct$corStruct)

  grps <- get_cor_grouping(mod)

  dR_dcor <- lapply(dR_dcor, function(x) {
    x_grps <- factor(grps, levels = names(x))
    attr(x, "groups") <- x_grps
    x
  })

  lapply(dR_dcor, build_var_cor_mats, mod = mod, sigma_scale = TRUE)
}

# Methods for different corStruct classes

dR_dcorStruct <- function(struct) UseMethod("dR_dcorStruct")

dR_dcorStruct.default <- function(struct) {
  cor_class <- class(struct)[[1]]
  stop(paste0("Derivatives not available for correlation structures of class ", cor_class, "."))
}

# corAR1

dR_dcorStruct.corAR1 <- function(struct) {
  cor_AR1 <- as.double(coef(struct, FALSE))
  covariate <- attr(struct, "covariate")
  if (!is.list(covariate)) covariate <- list(A = covariate)
  dR <- lapply(covariate, function(x) as.matrix(dist(x)) * cor_AR1^(as.matrix(dist(x)) - 1L))
  list(dR)
}

# corCAR1

dR_dcorStruct.corCAR1 <- function(struct) {
  cor_CAR1 <- as.double(coef(struct, FALSE))
  covariate <- attr(struct, "covariate")
  if (!is.list(covariate)) covariate <- list(A = covariate)
  dR <- lapply(covariate, function(x) as.matrix(dist(x)) * cor_CAR1^(as.matrix(dist(x)) - 1L))
  list(dR)
}

# corARMA

dR_dcorMA1 <- function(covariate, cor) {
  dist_mat <- as.matrix(dist(covariate))
  dist_mat[dist_mat != 1] <- 0
  dist_mat[dist_mat == 1] <- (1 - cor^2) / ((1 + cor^2)^2)
  return(dist_mat)
}

dR_dcorStruct.corMA1 <- function(struct) {
  cor_MA1 <- as.double(coef(struct, FALSE))
  covariate <- attr(struct, "covariate")
  if (!is.list(covariate)) covariate <- list(A = covariate)
  dR <- lapply(covariate, dR_dcorMA1, cor = cor_MA1)
  list(dR)
}

dR_dcorStruct.corARMA <- function(struct) {
  cor_ARMA <- coef(struct, FALSE)
  cor_name <- names(cor_ARMA)
  p <- length(grep("Phi", cor_name, value = TRUE))
  q <- length(grep("Theta", cor_name, value = TRUE))

  if (p == 1 & q == 0) {
    dR_dcorStruct.corAR1(struct)
  } else if (p == 0 & q == 1) {
    dR_dcorStruct.corMA1(struct)
  } else {
    stop("Derivatives not available for correlation structures of class corARMA, except for the simple cases of AR(1) or MA(1).")
  }
}

# corCompSymm

dR_dcorStruct.corCompSymm <- function(struct) {
  covariate <- attr(struct, "covariate")
  if (!is.list(covariate)) covariate <- list(A = covariate)
  dR <- lapply(covariate, function(x) 1L - diag(1L, nrow = length(x)))
  list(dR)
}

# corSymm

# return list of derivative matrices for one cor parameter

replace <- function(x, y, row, col) {
  row_index <- y == row
  col_index <- y == col
  x[row_index, col_index] <- x[col_index, row_index] <- 1L
  x
}

dR_dcor_index <- function(row, col, covariate) {
  R_null <- lapply(covariate, function(x) matrix(0L, nrow = length(x), ncol = length(x)))
  dR <- mapply(replace, x = R_null, y = covariate, MoreArgs = list(row = row, col = col), SIMPLIFY = FALSE)
  dR
}

# return a list of derivative matrices for all cor parameters

dR_dcorStruct.corSymm <- function(struct) {
  cor_Symm <- as.double(coef(struct, FALSE)) # parameters
  cor_q <- (1 + sqrt(1 + 8 * length(cor_Symm))) / 2 # number of cols/rows of R mat
  cor_index <- as.matrix(t(utils::combn(1:cor_q, 2))) # sort cor_index appropriately
  groups <- attr(struct, "groups")
  covariate <- attr(struct, "covariate")
  covariate <- lapply(covariate, function(x) as.integer(x + 1)) # add 1 to covariate to align with cor_index
  apply(cor_index, 1, function(t) dR_dcor_index(t[1], t[2], covariate = covariate))
}

#------------------------------------------------------------------------------
# First derivative matrices wrt sigma^2
#------------------------------------------------------------------------------

dV_dsigmasq <- function(mod) {

  fixed_sigma <- attr(mod$modelStruct, "fixedSigma")

  if (fixed_sigma) {
    NULL
  } else {
    list(build_var_cor_mats(mod, sigma_scale = FALSE))
  }

}

#------------------------------------------------------------------------------
# First derivative matrices wrt variance structures
#------------------------------------------------------------------------------

# Derivative of Sigma with respect to variance structure parameters

sdRds <- function(dsd_list, sd_list, R_list) {
  dsd_list <- Map(function(s, d) tcrossprod(s, d),
             s = sd_list, d = dsd_list)
  dV_list <- Map(function(dsd, R) (dsd + t(dsd)) * R,
                 dsd = dsd_list, R = R_list)
  attr(dV_list, "groups") <- attr(R_list, "groups")
  dV_list
}

dV_dvarStruct <- function(mod) {

  # No derivatives if there's no variance structure or only varFixed structure
  if (is.null(mod$modelStruct$varStruct) | inherits(mod$modelStruct$varStruct,"varFixed")) return(NULL)

  dsd_dvar <- dsd_dvarStruct(mod$modelStruct$varStruct)
  R_list <- build_corr_mats(mod)

  if (is.null(R_list)) {
    sort_order <- get_sort_order(mod)

    dsd_dvar <- lapply(dsd_dvar, function(x) x[sort_order]) # reorder based on input data

    wts <- nlme::varWeights(mod$modelStruct$varStruct)[sort_order]

    dV_dvar <- lapply(dsd_dvar, function(d) 2 * d * mod$sigma^2 / wts)

    groups <- if (is.null(mod$groups)) factor(1:mod$dims$N) else mod$groups[[1]]

    dV_list <- lapply(dV_dvar, function(v) {
      v_list <- tapply(v, groups, function(x) diag(x, nrow = length(x)), simplify = FALSE)
      attr(v_list, "groups") <- groups
      v_list
    })

  } else {
    cor_grps <- attr(mod$modelStruct$corStruct, "groups")
    wts <- nlme::varWeights(mod$modelStruct$varStruct)
    sigmasq_S_list <- split(mod$sigma^2 / wts, cor_grps)
    dsd_list <- lapply(dsd_dvar, split, f = cor_grps)
    dV_list <- lapply(dsd_list, sdRds, sd_list = sigmasq_S_list, R_list = R_list)

  }

  dV_list

}

# Methods for different varStruct classes

dsd_dvarStruct <- function(struct) UseMethod("dsd_dvarStruct")

dsd_dvarStruct.default <- function(struct) {
  var_class <- class(struct)[[1]]
  stop(paste0("Derivatives not available for variance structures of class ", var_class, "."))
}

# varIdent

dsd_dvarStruct.varIdent <- function(struct) {
  grps <- attr(struct, "groups")
  par_name <- names(coef(struct, FALSE)) # sd parameter name
  lapply(par_name, function(x) as.integer(x == grps))
}

# varExp

dsd_dvarExp <- function(val, grp, groups, covariate) {
  exp(covariate * val) * covariate * as.integer(grp == groups)
}

dsd_dvarStruct.varExp <- function(struct) {
  var_Exp <- coef(struct, FALSE)
  par_val <- as.double(var_Exp)
  par_name <- attr(var_Exp, "names")
  groups <- attr(struct, "groups")
  covariate <- attr(struct, "covariate")

  if (length(var_Exp) == 1) {

    covariate <- attr(struct, "covariate")
    list(exp(covariate * par_val) * covariate)

  } else{

    mapply(dsd_dvarExp, val = par_val, grp = par_name,
           MoreArgs = list(groups = groups, covariate = covariate),
           SIMPLIFY = FALSE)
  }
}

# varPower

dsd_dvarPower <- function(val, grp, groups, covariate) {
  abs_covariate <- abs(covariate)
  abs_covariate^val * log(abs_covariate) * as.integer(grp == groups)
}

dsd_dvarStruct.varPower <- function(struct) {
  var_Power <- coef(struct, FALSE)
  par_val <- as.double(var_Power)
  par_name <- attr(var_Power, "names")
  groups <- attr(struct, "groups")
  covariate <- attr(struct, "covariate")

  if (length(var_Power) == 1) {

    abs_covariate <- abs(covariate)
    list(abs_covariate^par_val * log(abs_covariate))

  } else {

    mapply(dsd_dvarPower, val = par_val, grp = par_name,
           MoreArgs = list(groups = groups, covariate = covariate),
           SIMPLIFY = FALSE)

  }
}

# varConstPower

# for one stratum (two parameters: const and power)

dsd_dConstPower1 <- function(x, val, covariate) {
  abs_covariate <- abs(covariate)
  if (x == "const") {
    rep(1, length(covariate))
  } else {
    abs_covariate^val[2] * log(abs_covariate) # [2] is the power par
  }
}

# for two or more strata (multiple const and power parameters)

dsd_dConstPower2 <- function(val, type, grp, groups, covariate) {
  abs_covariate <- abs(covariate)
  if (type == "const") {
    as.integer(grp == groups)
  } else {
    abs_covariate^val * log(abs_covariate) * as.integer(grp == groups)
  }
}

dsd_dvarStruct.varConstPower <- function(struct) {
  groups <- attr(struct, "groups")
  covariate <- attr(struct, "covariate")
  var_ConstPower <- coef(struct, FALSE)
  par_name <- names(var_ConstPower)
  par_val <- as.double(var_ConstPower)

  # indicates whether the par is a const or power, can be used for one stratum scenario
  par_type <- substr(par_name, 1, 5)

  # indicates the stratum of the par, cannot be used for one stratum scenario
  par_grp <- substring(par_name, 7)

  if (length(var_ConstPower) == 2) {

    lapply(par_name, dsd_dConstPower1, val = par_val, covariate = covariate)

  } else {

    mapply(dsd_dConstPower2, val = par_val, type = par_type, grp = par_grp,
           MoreArgs = list(groups = groups, covariate = covariate),
           SIMPLIFY = FALSE)

  }
}

Try the lmeInfo package in your browser

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

lmeInfo documentation built on April 17, 2023, 9:08 a.m.