|
| 1 | +#' Block-Band Matrix with Group Structure |
| 2 | +#' |
| 3 | +#' @description |
| 4 | +#' Construct a masked precision matrix where entries are kept only within |
| 5 | +#' specified group neighborhoods. Optionally generate Gaussian samples from |
| 6 | +#' the resulting precision matrix. |
| 7 | +#' |
| 8 | +#' @param mat A p-by-p precision-like matrix specifying the base matrix to be |
| 9 | +#' masked. |
| 10 | +#' |
| 11 | +#' @param membership An integer vector specifying the group membership. |
| 12 | +#' The length of \code{membership} must be consistent with the dimension p. |
| 13 | +#' |
| 14 | +#' @param within.diag A boolean (default = TRUE) specifying whether within-group |
| 15 | +#' blocks keep only diagonal entries (i.e., identity-like structure within each |
| 16 | +#' group). |
| 17 | +#' |
| 18 | +#' @param neighbor.range An integer (default = 1) specifying the neighbor range, |
| 19 | +#' where groups whose labels differ by at most \code{neighbor.range} are |
| 20 | +#' considered neighbors and kept in the mask. |
| 21 | +#' |
| 22 | +#' @param group.diag An integer vector (default = NULL) specifying which |
| 23 | +#' within-group blocks should keep diagonal-only structure when |
| 24 | +#' \code{within.diag = FALSE}. |
| 25 | +#' |
| 26 | +#' @param n An integer (default = NULL) specifying the sample size for |
| 27 | +#' generating Gaussian data from the resulting precision matrix. If \code{NULL}, |
| 28 | +#' no data are generated. |
| 29 | +#' |
| 30 | +#' @param seed An integer (default = 1) specifying the random seed for |
| 31 | +#' reproducibility. |
| 32 | +#' |
| 33 | +#' @importFrom MASS mvrnorm |
| 34 | +#' |
| 35 | +#' @return A list containing: |
| 36 | +#' \describe{ |
| 37 | +#' \item{Omega}{The masked precision matrix.} |
| 38 | +#' \item{Sigma}{The covariance matrix, i.e., the inverse of \code{Omega}.} |
| 39 | +#' \item{sparsity}{Proportion of zero entries in \code{Omega}.} |
| 40 | +#' \item{X}{If \code{n} is not \code{NULL}, an \code{n}-by-\code{p} matrix of |
| 41 | +#' Gaussian observations sampled from \eqn{\mathcal{N}(0, \Sigma)}.} |
| 42 | +#' \item{membership}{An integer vector specifying the group membership.} |
| 43 | +#' } |
| 44 | +#' |
| 45 | +#' @export |
| 46 | + |
| 47 | +blockband <- function(mat, membership, |
| 48 | + within.diag = TRUE, neighbor.range = 1, group.diag = NULL, |
| 49 | + n, seed = 1) { |
| 50 | + |
| 51 | + stopifnot(length(membership) == ncol(mat)) |
| 52 | + |
| 53 | + ## determine which entries to keep: membership within 'neighbor.range' |
| 54 | + mask <- abs(outer(membership, membership, `-`)) <= neighbor.range |
| 55 | + |
| 56 | + ## adjust within-group blocks |
| 57 | + if (within.diag) { |
| 58 | + ## within-group blocks -> diagonal matrix |
| 59 | + block_idx <- split(seq_along(membership), membership) |
| 60 | + for (idx in block_idx) { |
| 61 | + mask[idx, idx] <- FALSE |
| 62 | + diag(mask[idx, idx]) <- TRUE |
| 63 | + } |
| 64 | + } else { |
| 65 | + if (!is.null(group.diag)) { |
| 66 | + for (group_idx in group.diag) { |
| 67 | + idx <- which(membership == group_idx) |
| 68 | + mask[idx, idx] <- FALSE |
| 69 | + diag(mask[idx, idx]) <- TRUE |
| 70 | + } |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + ## apply mask to matrix |
| 75 | + Omega <- mat * mask |
| 76 | + ## compute covariance |
| 77 | + Sigma <- solve(Omega) |
| 78 | + |
| 79 | + result <- list(Omega = Omega, Sigma = Sigma, |
| 80 | + sparsity = sum(Omega == 0) / length(Omega), |
| 81 | + membership = membership) |
| 82 | + |
| 83 | + if (!is.null(n)) { |
| 84 | + set.seed(seed) |
| 85 | + ## sample |
| 86 | + result$X <- MASS::mvrnorm(n = n, mu = rep(0, ncol(Omega)), Sigma = Sigma) |
| 87 | + } |
| 88 | + |
| 89 | + return(result) |
| 90 | +} |
| 91 | + |
0 commit comments