Skip to content

Commit

Permalink
Moved listw2sparse and multi_listw2sparse from Voyager to SFE
Browse files Browse the repository at this point in the history
  • Loading branch information
lambdamoses committed Aug 7, 2024
1 parent c9a660f commit 1870779
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 0 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Collate:
'image.R'
'int_dimData.R'
'internal-Voyager.R'
'listw2sparse.R'
'localResults.R'
'read.R'
'reexports.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export(imgData)
export(imgRaster)
export(imgSource)
export(isFull)
export(listw2sparse)
export(localResult)
export(localResultAttrs)
export(localResultFeatures)
Expand All @@ -112,6 +113,7 @@ export(localResults)
export(logcounts)
export(mirror)
export(mirrorImg)
export(multi_listw2sparse)
export(nucSeg)
export(origin)
export(read10xVisiumSFE)
Expand Down
83 changes: 83 additions & 0 deletions R/listw2sparse.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# As in MatrixExtra, only for Csparse for now
.empty_dgc <- function(nrow, ncol) {
out <- new("dgCMatrix")
out@Dim <- as.integer(c(nrow, ncol))
out@p <- integer(ncol+1L)
out
}

#' Convert listw into sparse adjacency matrix
#'
#' Edge weights are used in the adjacency matrix. Because most elements of the
#' matrix are 0, using sparse matrix greatly reduces memory use.
#'
#' @param listw A \code{listw} object for spatial neighborhood graph.
#' @return A sparse \code{dgCMatrix}, whose row represents each cell or spot and
#' whose columns represent the neighbors. The matrix does not have to be
#' symmetric. If \code{region.id} is present in the \code{listw} object, then
#' it will be the row and column names of the output matrix.
#' @export
#' @importFrom Matrix sparseMatrix
#' @examples
#' library(SFEData)
#' sfe <- McKellarMuscleData("small")
#' g <- findVisiumGraph(sfe)
#' mat <- listw2sparse(g)
listw2sparse <- function(listw) {
i <- rep(seq_along(listw$neighbours), times = card(listw$neighbours))
j <- unlist(listw$neighbours)
x <- unlist(listw$weights)
n <- length(listw$neighbours)
region_id <- attr(listw$neighbours, "region.id")
sparseMatrix(i = i, j = j, x = x, dims = rep(n, 2),
dimnames = list(region_id, region_id))
}

#' Convert multiple listw graphs into a single sparse adjacency matrix
#'
#' Each sample in the SFE object has a separate spatial neighborhood graph.
#' Spatial analyses performed jointly on multiple samples require a combined
#' spatial neighborhood graph from the different samples, where the different
#' samples would be disconnected components of the graph. This combined
#' adjacency matrix can be used in MULTISPATI PCA.
#'
#' @param listws A list of \code{listw} objects.
#' @return A sparse \code{dgCMatrix} of the combined spatial neighborhood graph,
#' with the original spatial neighborhood graphs of the samples on the diagonal.
#' When the input is an SFE object, the rows and columns will match the column
#' names of the SFE object.
#' @export
#' @examples
#' # example code
#'
multi_listw2sparse <- function(listws) {
slices <- list()
n <- length(listws)
mats <- lapply(listws, listw2sparse)
ncells <- vapply(mats, nrow, FUN.VALUE = integer(1))
region_ids <- lapply(listws, function(l) attr(l$neighbours, "region.id"))
tot <- sum(ncells)
prev <- 0
next_n <- tot
prev_inds <- 0
next_inds <- 1
for (i in seq_along(listws)) {
n_curr <- ncells[i]
next_n <- next_n - n_curr
next_inds <- next_inds + 1
if (prev > 0) {
prev_m <- .empty_dgc(nrow = prev, ncol = n_curr)
rownames(prev_m) <- unlist(region_ids[seq_len(prev_inds)])
o <- rbind(prev_m, mats[[i]])
} else o <- mats[[i]]
if (next_n > 0) {
next_m <- .empty_dgc(nrow = next_n, ncol = n_curr)
rownames(next_m) <- unlist(region_ids[seq(next_inds, n, by = 1)])
o <- rbind(o, next_m)
}
slices[[i]] <- o
prev <- prev + n_curr
prev_inds <- prev_inds + 1
}
do.call(cbind, slices)
}
27 changes: 27 additions & 0 deletions man/listw2sparse.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions man/multi_listw2sparse.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions tests/testthat/test-listw2sparse.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
library(SFEData)
library(spdep)
library(sf)

sfe <- McKellarMuscleData("small")
g <- findVisiumGraph(sfe)

test_that("listw2sparse gives correct results", {
mat <- listw2sparse(g)
expect_s4_class(mat, "dgCMatrix")
expect_equal(nrow(mat), ncol(sfe))
expect_equal(ncol(mat), ncol(sfe))
expect_equal(Matrix::rowSums(mat > 0), card(g$neighbours), ignore_attr = TRUE)
m2 <- listw2mat(g)
expect_equal(as.matrix(mat), m2, ignore_attr = TRUE)
expect_equal(rownames(mat), rownames(m2))
expect_equal(rownames(mat), colnames(mat))
})

# Add a singleton to g
g_single <- g
g_single$neighbours <- c(g_single$neighbours, 0L)
class(g_single$neighbours) <- "nb"
attr(g_single, "region.id") <- c(attr(g_single, "region.id"), "foo")
g_single$weights <- c(g_single$weights, list(NULL))

test_that("Deal with singletons in listw2sparse", {
mat <- listw2mat(g_single)
n <- length(g_single$neighbours)
expect_equal(nrow(mat), n)
expect_equal(ncol(mat), n)
expect_equal(Matrix::rowSums(mat)[n], 0, ignore_attr = TRUE)
})

nb1 <- grid2nb(d = c(5,5))
nb2 <- grid2nb(d = c(3,3))
attr(nb1, "region.id") <- LETTERS[1:25]
attr(nb2, "region.id") <- letters[1:9]
l1 <- nb2listw(nb1)
l2 <- nb2listw(nb2)
listws <- list(l1, l2)
names_expect <- c(LETTERS[1:25], letters[1:9])
test_that("Convert list of listws to one adjacency matrix", {
mat <- multi_listw2sparse(listws)
expect_s4_class(mat, "dgCMatrix")
l_expect <- length(nb1) + length(nb2)
expect_equal(nrow(mat), l_expect)
expect_equal(ncol(mat), l_expect)
expect_equal(rownames(mat), names_expect)
expect_equal(colnames(mat), names_expect)
expect_equal(as.matrix(mat[1:25,1:25]), listw2mat(l1), ignore_attr = TRUE)
expect_equal(as.matrix(mat[26:34,26:34]), listw2mat(l2), ignore_attr = TRUE)
expect_equal(sum(mat[26:34, 1:25]), 0)
expect_equal(sum(mat[1:25, 26:34]), 0)
})

0 comments on commit 1870779

Please sign in to comment.