diff --git a/R/adjacency.R b/R/adjacency.R index c24ad085a1..930790eb47 100644 --- a/R/adjacency.R +++ b/R/adjacency.R @@ -1,4 +1,3 @@ - #' Create graphs from adjacency matrices #' #' @description @@ -119,15 +118,15 @@ graph.adjacency <- function(adjmatrix, mode = c("directed", "undirected", "max", #' @examples #' #' g1 <- sample( -#' x = 0:1, size = 100, replace = TRUE, -#' prob = c(0.9, 0.1) -#' ) %>% +#' x = 0:1, size = 100, replace = TRUE, +#' prob = c(0.9, 0.1) +#' ) %>% #' matrix(ncol = 10) %>% #' graph_from_adjacency_matrix() #' #' g2 <- sample( -#' x = 0:5, size = 100, replace = TRUE, -#' prob = c(0.9, 0.02, 0.02, 0.02, 0.02, 0.02) +#' x = 0:5, size = 100, replace = TRUE, +#' prob = c(0.9, 0.02, 0.02, 0.02, 0.02, 0.02) #' ) %>% #' matrix(ncol = 10) %>% #' graph_from_adjacency_matrix(weighted = TRUE) @@ -193,8 +192,8 @@ graph.adjacency <- function(adjmatrix, mode = c("directed", "undirected", "max", #' x #' } #' expected_g8_weights <- non_zero_sort( -#' halve_diag(adj_matrix + t(adj_matrix) -#' )[lower.tri(adj_matrix, diag = TRUE)]) +#' halve_diag(adj_matrix + t(adj_matrix))[lower.tri(adj_matrix, diag = TRUE)] +#' ) #' actual_g8_weights <- sort(E(g8)$weight) #' all(expected_g8_weights == actual_g8_weights) #' @@ -229,7 +228,6 @@ graph_from_adjacency_matrix <- function(adjmatrix, ), weighted = NULL, diag = TRUE, add.colnames = NULL, add.rownames = NA) { - mode <- igraph.match.arg(mode) if (!is.matrix(adjmatrix) && !inherits(adjmatrix, "Matrix")) { @@ -377,157 +375,68 @@ mysummary <- function(x) { result } +pmax_AB <- function(A,B) { + change <- A < B + A[change] <- B[change] + A +} -graph.adjacency.sparse <- function(adjmatrix, mode, weighted = NULL, diag = TRUE) { +pmin_AB <- function(A, B) { + change <- A > B + A[change] <- B[change] + A +} + +graph.adjacency.sparse <- function(adjmatrix, mode, weighted = NULL, diag = TRUE, call = rlang::caller_env()) { if (!is.null(weighted)) { if (is.logical(weighted) && weighted) { weighted <- "weight" } if (!is.character(weighted)) { - stop("invalid value supplied for `weighted' argument, please see docs.") + cli::cli_abort("Invalid value supplied for `weighted' argument, please see docs.", call = call) } } if (nrow(adjmatrix) != ncol(adjmatrix)) { - stop("not a square matrix") + cli::cli_abort("Not a square matrix", call = call) } vc <- nrow(adjmatrix) + # Exit early for empty graphs + if (vc == 1 || Matrix::nnzero(adjmatrix) == 0) { + return(make_empty_graph(n = vc, directed = (mode == "directed"))) + } - ## to remove non-redundancies that can persist in a dgtMatrix if (inherits(adjmatrix, "dgTMatrix")) { adjmatrix <- as(adjmatrix, "CsparseMatrix") } else if (inherits(adjmatrix, "ddiMatrix")) { adjmatrix <- as(adjmatrix, "CsparseMatrix") } - if (mode == "directed") { - ## DIRECTED - el <- mysummary(adjmatrix) - if (!diag) { - el <- el[el[, 1] != el[, 2], ] - } - } else if (mode == "undirected") { - ## UNDIRECTED, must be symmetric if weighted + if (mode == "undirected") { if (!is.null(weighted) && !Matrix::isSymmetric(adjmatrix)) { - stop("Please supply a symmetric matrix if you want to create a weighted graph with mode=UNDIRECTED.") - } - if (diag) { - adjmatrix <- Matrix::tril(adjmatrix) - } else { - if (vc == 1) { - # Work around Matrix glitch - adjmatrix <- as(matrix(0), "dgCMatrix") - } else { - adjmatrix <- Matrix::tril(adjmatrix, -1) - } - } - el <- mysummary(adjmatrix) - rm(adjmatrix) - } else if (mode == "max") { - ## MAXIMUM - el <- mysummary(adjmatrix) - rm(adjmatrix) - if (!diag) { - el <- el[el[, 1] != el[, 2], ] - } - el <- el[el[, 3] != 0, ] - w <- el[, 3] - el <- el[, 1:2] - el <- cbind(pmin(el[, 1], el[, 2]), pmax(el[, 1], el[, 2])) - o <- order(el[, 1], el[, 2]) - el <- el[o, , drop = FALSE] - w <- w[o] - if (nrow(el) > 1) { - dd <- el[2:nrow(el), 1] == el[1:(nrow(el) - 1), 1] & - el[2:nrow(el), 2] == el[1:(nrow(el) - 1), 2] - dd <- which(dd) - if (length(dd) > 0) { - mw <- pmax(w[dd], w[dd + 1]) - w[dd] <- mw - w[dd + 1] <- mw - el <- el[-dd, , drop = FALSE] - w <- w[-dd] - } - } - el <- cbind(el, w) - } else if (mode == "upper") { - ## UPPER - if (diag) { - adjmatrix <- Matrix::triu(adjmatrix) - } else { - adjmatrix <- Matrix::triu(adjmatrix, 1) - } - el <- mysummary(adjmatrix) - rm(adjmatrix) - if (!diag) { - el <- el[el[, 1] != el[, 2], ] + cli::cli_abort( + "Please supply a symmetric matrix if you want to create a weighted graph with mode=UNDIRECTED.", + call = call + ) } + adjmatrix <- Matrix::tril(adjmatrix) } else if (mode == "lower") { - ## LOWER - if (diag) { - adjmatrix <- Matrix::tril(adjmatrix) - } else { - if (vc == 1) { - # Work around Matrix glitch - adjmatrix <- as(matrix(0), "dgCMatrix") - } else { - adjmatrix <- Matrix::tril(adjmatrix, -1) - } - } - el <- mysummary(adjmatrix) - rm(adjmatrix) - if (!diag) { - el <- el[el[, 1] != el[, 2], ] - } - } else if (mode == "min") { - ## MINIMUM - adjmatrix <- sign(adjmatrix) * sign(Matrix::t(adjmatrix)) * adjmatrix - el <- mysummary(adjmatrix) - rm(adjmatrix) - if (!diag) { - el <- el[el[, 1] != el[, 2], ] - } - el <- el[el[, 3] != 0, ] - w <- el[, 3] - el <- el[, 1:2] - el <- cbind(pmin(el[, 1], el[, 2]), pmax(el[, 1], el[, 2])) - o <- order(el[, 1], el[, 2]) - el <- el[o, ] - w <- w[o] - if (nrow(el) > 1) { - dd <- el[2:nrow(el), 1] == el[1:(nrow(el) - 1), 1] & - el[2:nrow(el), 2] == el[1:(nrow(el) - 1), 2] - dd <- which(dd) - if (length(dd) > 0) { - mw <- pmin(w[dd], w[dd + 1]) - w[dd] <- mw - w[dd + 1] <- mw - el <- el[-dd, ] - w <- w[-dd] - } - } - el <- cbind(el, w) + adjmatrix <- Matrix::tril(adjmatrix) + } else if (mode == "upper") { + adjmatrix <- Matrix::triu(adjmatrix) } else if (mode == "plus") { - ## PLUS adjmatrix <- adjmatrix + Matrix::t(adjmatrix) - if (diag) { - adjmatrix <- Matrix::tril(adjmatrix) - } else { - if (vc == 1) { - # Work around Matrix glitch - adjmatrix <- as(matrix(0), "dgCMatrix") - } else { - adjmatrix <- Matrix::tril(adjmatrix, -1) - } - } - el <- mysummary(adjmatrix) - rm(adjmatrix) - if (diag) { - loop <- el[, 1] == el[, 2] - el[loop, 3] <- el[loop, 3] / 2 - } - el <- el[el[, 3] != 0, ] + adjmatrix <- Matrix::tril(adjmatrix) + } else if (mode == "max") { + adjmatrix <- pmax_AB(Matrix::tril(adjmatrix), Matrix::t(Matrix::triu(adjmatrix))) + } else if (mode == "min") { + adjmatrix <- pmin_AB(Matrix::tril(adjmatrix), Matrix::t(Matrix::triu(adjmatrix))) + adjmatrix <- Matrix::drop0(adjmatrix) + } + el <- mysummary(adjmatrix) + if (!diag) { + el <- el[el[, 1] != el[, 2], ] } if (!is.null(weighted)) { @@ -536,7 +445,12 @@ graph.adjacency.sparse <- function(adjmatrix, mode, weighted = NULL, diag = TRUE names(weight) <- weighted res <- add_edges(res, edges = t(as.matrix(el[, 1:2])), attr = weight) } else { - edges <- unlist(apply(el, 1, function(x) rep(unname(x[1:2]), x[3]))) + if (max(el[, 3]) == 1) { + edges <- as.vector(t(el[, 1:2])) + } else { + row_repeats <- rep(seq_len(nrow(el)), times = el[, 3]) + edges <- as.vector(t(el[row_repeats, 1:2])) + } res <- make_graph(n = vc, edges, directed = (mode == "directed")) } res diff --git a/tests/testthat/test-adjacency.R b/tests/testthat/test-adjacency.R index d29544bb49..f072b456f1 100644 --- a/tests/testthat/test-adjacency.R +++ b/tests/testthat/test-adjacency.R @@ -302,7 +302,7 @@ test_that("graph_from_adjacency_matrix() works", { }) test_that("graph_from_adjacency_matrix() works -- dgCMatrix", { - skip_if_not_installed("Matrix") + skip_if_not_installed("Matrix",minimum_version = "1.6.0") M1 <- rbind( c(0, 0, 1, 1), @@ -597,7 +597,7 @@ test_that("graph_from_adjacency_matrix() snapshot", { }) test_that("graph_from_adjacency_matrix() snapshot for sparse matrices", { - skip_if_not_installed("Matrix") + skip_if_not_installed("Matrix", minimum_version = "1.6.0") rlang::local_options(lifecycle_verbosity = "warning") @@ -657,3 +657,60 @@ test_that("weighted graph_from_adjacency_matrix() works on integer matrices", { g <- graph_from_adjacency_matrix(data, weighted = TRUE) expect_equal(as.matrix(g[]), data) }) + +test_that("sparse/dense matrices no loops works",{ + skip_if_not_installed("Matrix", minimum_version = "1.6.0") + A <- diag(1, 5) + A[1, 2] <- 1 + g <- graph_from_adjacency_matrix(A, diag = FALSE) + expect_equal(ecount(g), 1) + expect_equal(get_edge_ids(g, c(1, 2)), 1) + + A <- as(A, "dgCMatrix") + g <- graph_from_adjacency_matrix(A, diag = FALSE) + expect_equal(ecount(g), 1) + expect_equal(get_edge_ids(g,c(1, 2)), 1) + +}) + +test_that("sparse/dense matrices multiple works",{ + skip_if_not_installed("Matrix", minimum_version = "1.6.0") + A <- matrix(0, 5, 5) + A[1, 2] <- 3 + g <- graph_from_adjacency_matrix(A, diag = FALSE, weighted = FALSE) + expect_equal(ecount(g), 3) + expect_equal(as_edgelist(g), matrix(c(1, 2), 3, 2, byrow = TRUE)) + + A <- as(A,"dgCMatrix") + g <- graph_from_adjacency_matrix(A,diag = FALSE) + expect_equal(ecount(g), 3) + expect_equal(as_edgelist(g), matrix(c(1, 2), 3, 2, byrow = TRUE)) + +}) + +test_that("sparse/dense matrices min/max/plus",{ + skip_if_not_installed("Matrix", minimum_version = "1.6.0") + A <- matrix(0, 5, 5) + A[1, 2] <- 3 + A[2, 1] <- 2 + g <- graph_from_adjacency_matrix(A, diag = FALSE, mode = "max", weighted = TRUE) + expect_equal(E(g)$weight[1], 3) + + g <- graph_from_adjacency_matrix(A, diag = FALSE, mode = "min", weighted = TRUE) + expect_equal(E(g)$weight[1], 2) + + g <- graph_from_adjacency_matrix(A, diag = FALSE, mode = "plus", weighted = TRUE) + expect_equal(E(g)$weight[1], 5) + + A <- as(A,"dgCMatrix") + g <- graph_from_adjacency_matrix(A, diag = FALSE, mode = "max", weighted = TRUE) + expect_equal(E(g)$weight[1], 3) + + g <- graph_from_adjacency_matrix(A, diag = FALSE, mode = "min", weighted = TRUE) + expect_equal(E(g)$weight[1], 2) + + g <- graph_from_adjacency_matrix(A, diag = FALSE, mode = "plus", weighted = TRUE) + expect_equal(E(g)$weight[1], 5) + + +}) \ No newline at end of file