|
| 1 | +#' Visualize a Matrix with Group Boundaries |
| 2 | +#' |
| 3 | +#' @description |
| 4 | +#' Visualize a matrix (e.g., a precision, covariance, or adjacency matrix) as a |
| 5 | +#' heatmap with optional group-based reordering and dashed boundary lines |
| 6 | +#' separating group blocks. |
| 7 | +#' |
| 8 | +#' @param mat A p-by-p matrix. |
| 9 | +#' |
| 10 | +#' @param membership An integer vector specifying the group membership. |
| 11 | +#' The length of \code{membership} must be consistent with the dimension p. |
| 12 | +#' |
| 13 | +#' @param reorder_by_group A boolean (default = \code{FALSE}) specifying whether |
| 14 | +#' to reorder both rows and columns of \code{mat} according to \code{membership}, |
| 15 | +#' such that variables from the same group are contiguous. |
| 16 | +#' |
| 17 | +#' @param colors A vector of colors specifying an n-color gradient scale for |
| 18 | +#' the fill aesthetics. |
| 19 | +#' |
| 20 | +#' @import ggplot2 |
| 21 | +#' @importFrom grDevices colorRampPalette |
| 22 | +#' @importFrom scales rescale |
| 23 | +#' |
| 24 | +#' @return |
| 25 | +#' A \code{ggplot2} heatmap showing the matrix entries. Dashed lines indicate |
| 26 | +#' group boundaries. The plot title also reports matrix dimension and sparsity. |
| 27 | +#' |
| 28 | +#' @examples |
| 29 | +#' ## reproducibility for everything |
| 30 | +#' set.seed(1234) |
| 31 | +#' |
| 32 | +#' ## user-defined sampler |
| 33 | +#' my_gamma <- function(n) { |
| 34 | +#' rgamma(n, shape = 20, scale = 5) |
| 35 | +#' } |
| 36 | +#' |
| 37 | +#' ## block-structured precision matrix based on SBM |
| 38 | +#' sim <- gen_prec_sbm(p = 100, K = 5, |
| 39 | +#' within.prob = 0.5, between.prob = 0.05, |
| 40 | +#' weight.dists = list(my_gamma, "unif"), |
| 41 | +#' weight.paras = list(NULL, c(min = 0, max = 1)), |
| 42 | +#' min.eig = 0.1) |
| 43 | +#' |
| 44 | +#' ## visualization |
| 45 | +#' visualize(sim$Omega, sim$membership) |
| 46 | +#' |
| 47 | +#' @export |
| 48 | + |
| 49 | +visualize <- function(mat, membership, reorder_by_group = FALSE, |
| 50 | + colors = colorRampPalette( |
| 51 | + c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", |
| 52 | + "yellow", "#FF7F00", "red", "#7F0000"))(256)) { |
| 53 | + |
| 54 | + p <- ncol(mat) |
| 55 | + if (length(membership) != p) { |
| 56 | + stop(sprintf("Length of 'membership' (%d) must equal the matrix dimension p (%d).", |
| 57 | + length(membership), p)) |
| 58 | + } |
| 59 | + |
| 60 | + ## optionally reorder rows/cols by group membership |
| 61 | + if (reorder_by_group) { |
| 62 | + o <- order(membership) |
| 63 | + mat <- mat[o, o] |
| 64 | + membership <- membership[o] |
| 65 | + } |
| 66 | + |
| 67 | + ## compute group sizes and boundary positions for dashed lines |
| 68 | + grp_sizes <- table(membership) |
| 69 | + cuts <- cumsum(grp_sizes) |
| 70 | + bnds <- cuts[-length(cuts)] + 0.5 ## boundaries between groups |
| 71 | + y_bnds <- p - bnds + 1 ## flipped y, note: scale_y_discrete(limits = rev) |
| 72 | + |
| 73 | + ## declare |
| 74 | + Col <- Row <- value <- NULL |
| 75 | + |
| 76 | + ## plot data |
| 77 | + labs <- paste0("V", seq_len(p)) |
| 78 | + plotData <- data.frame( |
| 79 | + Row = factor(rep(labs, times = p), levels = labs), |
| 80 | + Col = factor(rep(labs, each = p), levels = labs), |
| 81 | + value = as.vector(mat), |
| 82 | + check.names = FALSE |
| 83 | + ) |
| 84 | + # plotData <- as.data.frame(mat) %>% |
| 85 | + # mutate(Row = factor(paste0("V", rownames(.)), |
| 86 | + # levels = paste0("V", rownames(.)))) %>% |
| 87 | + # pivot_longer(-Row, names_to = "Col", values_to = "value") %>% |
| 88 | + # mutate(Col = factor(Col, levels = levels(Row))) |
| 89 | + |
| 90 | + ## sparsity (proportion of zero entries) |
| 91 | + sparsity <- round(sum(mat == 0) / length(mat), 4) |
| 92 | + |
| 93 | + ## zero -> NA for better white rendering |
| 94 | + plotData$value[plotData$value == 0] <- NA |
| 95 | + |
| 96 | + ## color scaling range |
| 97 | + vmin <- min(plotData$value, na.rm = TRUE) |
| 98 | + vmax <- max(plotData$value, na.rm = TRUE) |
| 99 | + |
| 100 | + ggplot(plotData, aes(x = Col, y = Row, fill = value)) + |
| 101 | + coord_fixed() + |
| 102 | + geom_tile() + |
| 103 | + guides(fill = guide_colourbar(title = NULL, barwidth = 0.5, barheight = 5)) + |
| 104 | + scale_y_discrete(limits = rev) + |
| 105 | + scale_fill_gradientn(colours = colors, |
| 106 | + values = rescale(c(vmin, 0, vmax)), |
| 107 | + limits = c(vmin, vmax), |
| 108 | + na.value = "white") + |
| 109 | + geom_vline(xintercept = bnds, linetype = "dashed") + |
| 110 | + geom_hline(yintercept = y_bnds, linetype = "dashed") + |
| 111 | + labs(x = NULL, y = NULL, |
| 112 | + title = sprintf("p = %d, Sparsity = %s", ncol(mat), sparsity)) + |
| 113 | + theme_bw() + |
| 114 | + theme(axis.text = element_blank(), |
| 115 | + axis.ticks = element_blank(), |
| 116 | + legend.ticks = element_blank(), |
| 117 | + # plot.margin = margin(1, 1, 1, 1, "mm"), |
| 118 | + plot.title = element_text(hjust = .5)) |
| 119 | +} |
0 commit comments