diff --git a/R/centrality.R b/R/centrality.R index ea707ea6dd7..577a5765f7c 100644 --- a/R/centrality.R +++ b/R/centrality.R @@ -1760,11 +1760,12 @@ bonpow.dense <- function( loops = FALSE, exponent = 1, rescale = FALSE, - tol = 1e-7 + tol = 1e-7, + weights = NULL ) { ensure_igraph(graph) - d <- as_adjacency_matrix(graph) + d <- as_adjacency_matrix(graph, weights = weights, sparse = FALSE) if (!loops) { diag(d) <- 0 } @@ -1788,7 +1789,8 @@ bonpow.sparse <- function( loops = FALSE, exponent = 1, rescale = FALSE, - tol = 1e-07 + tol = 1e-07, + weights = NULL ) { ## remove loops if requested if (!loops) { @@ -1798,13 +1800,13 @@ bonpow.sparse <- function( vg <- vcount(graph) ## sparse adjacency matrix - d <- as_adjacency_matrix(graph, sparse = TRUE) + d <- as_adjacency_matrix(graph, weights = weights, sparse = TRUE) ## sparse identity matrix id <- as(Matrix::Matrix(diag(vg), doDiag = FALSE), "generalMatrix") ## solve it - ev <- Matrix::solve(id - exponent * d, degree(graph, mode = "out"), tol = tol) + ev <- Matrix::solve(id - exponent * d, Matrix::rowSums(d), tol = tol) if (rescale) { ev <- ev / sum(ev) @@ -1884,6 +1886,7 @@ bonpow.sparse <- function( #' @param sparse Logical scalar, whether to use sparse matrices for the #' calculation. The \sQuote{Matrix} package is required for sparse matrix #' support +#' @inheritParams as_adjacency_matrix #' @return A vector, containing the centrality scores. #' @note This function was ported (i.e. copied) from the SNA package. #' @section Warning : Singular adjacency matrices cause no end of headaches for @@ -1936,13 +1939,30 @@ power_centrality <- function( exponent = 1, rescale = FALSE, tol = 1e-7, - sparse = TRUE + sparse = TRUE, + weights = NULL ) { nodes <- as_igraph_vs(graph, nodes) if (sparse) { - res <- bonpow.sparse(graph, nodes, loops, exponent, rescale, tol) + res <- bonpow.sparse( + graph, + nodes, + loops, + exponent, + rescale, + tol, + weights = weights + ) } else { - res <- bonpow.dense(graph, nodes, loops, exponent, rescale, tol) + res <- bonpow.dense( + graph, + nodes, + loops, + exponent, + rescale, + tol, + weights = weights + ) } if (igraph_opt("add.vertex.names") && is_named(graph)) { @@ -1966,25 +1986,7 @@ alpha.centrality.dense <- function( exo <- rep(exo, length.out = vcount(graph)) exo <- matrix(exo, ncol = 1) - if (is.null(weights) && "weight" %in% edge_attr_names(graph)) { - ## weights == NULL and there is a "weight" edge attribute - attr <- "weight" - } else if (is.null(weights)) { - ## weights == NULL, but there is no "weight" edge attribute - attr <- NULL - } else if (is.character(weights) && length(weights) == 1) { - ## name of an edge attribute, nothing to do - attr <- weights - } else if (!all(is.na(weights))) { - ## weights != NULL and weights != rep(NA, x) - graph <- set_edge_attr(graph, "weight", value = as.numeric(weights)) - attr <- "weight" - } else { - ## weights != NULL, but weights == rep(NA, x) - attr <- NULL - } - - d <- t(as_adjacency_matrix(graph, attr = attr, sparse = FALSE)) + d <- t(as_adjacency_matrix(graph, weights = weights, sparse = FALSE)) if (!loops) { diag(d) <- 0 } @@ -2013,25 +2015,7 @@ alpha.centrality.sparse <- function( graph <- simplify(graph, remove.multiple = FALSE, remove.loops = TRUE) } - if (is.null(weights) && "weight" %in% edge_attr_names(graph)) { - ## weights == NULL and there is a "weight" edge attribute - attr <- "weight" - } else if (is.null(weights)) { - ## weights == NULL, but there is no "weight" edge attribute - attr <- NULL - } else if (is.character(weights) && length(weights) == 1) { - ## name of an edge attribute, nothing to do - attr <- weights - } else if (!all(is.na(weights))) { - ## weights != NULL and weights != rep(NA, x) - graph <- set_edge_attr(graph, "weight", value = as.numeric(weights)) - attr <- "weight" - } else { - ## weights != NULL, but weights == rep(NA, x) - attr <- NULL - } - - M <- Matrix::t(as_adjacency_matrix(graph, attr = attr, sparse = TRUE)) + M <- Matrix::t(as_adjacency_matrix(graph, weights = weights, sparse = TRUE)) ## Create an identity matrix M2 <- Matrix::sparseMatrix( @@ -2082,11 +2066,7 @@ alpha.centrality.sparse <- function( #' the same factor for every node, or a vector giving the factor for every #' vertex. Note that too long vectors will be truncated and too short vectors #' will be replicated to match the number of vertices. -#' @param weights A character scalar that gives the name of the edge attribute -#' to use in the adjacency matrix. If it is `NULL`, then the -#' \sQuote{weight} edge attribute of the graph is used, if there is one. -#' Otherwise, or if it is `NA`, then the calculation uses the standard -#' adjacency matrix. +#' @inheritParams as_adjacency_matrix #' @param tol Tolerance for near-singularities during matrix inversion, see #' [solve()]. #' @param sparse Logical scalar, whether to use sparse matrices for the diff --git a/R/conversion.R b/R/conversion.R index 462c047e42e..be7dea10aef 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -219,10 +219,145 @@ get.adjedgelist <- function( # ################################################################### +# Resolve the user-facing `weights` argument into a numeric vector +# suitable for downstream consumers. +# +# Conventions (match the rest of rigraph, e.g. shortest_paths()): +# weights = NULL -> auto-pickup of "weight" edge attribute, else unweighted +# weights = NA -> explicitly unweighted (ignores any "weight" attribute) +# weights = -> length-1 edge attribute name +# weights = -> numeric/logical vector of length ecount(graph) +# +# Returns: numeric() (empty) for unweighted, or a numeric vector of length +# ecount(graph) otherwise. +resolve_edge_weights <- function( + graph, + weights, + attr = deprecated(), + fn = "function", + call = rlang::caller_env(), + user_env = rlang::caller_env(2) +) { + if (lifecycle::is_present(attr)) { + lifecycle::deprecate_soft( + "3.0.0", + sprintf("%s(attr = )", fn), + sprintf("%s(weights = )", fn), + user_env = user_env + ) + weights <- attr + } + + if (is.null(weights)) { + if ("weight" %in% edge_attr_names(graph)) { + return(edge_attr_as_weights(graph, "weight", call)) + } + return(numeric()) + } + + if (all(is.na(weights))) { + return(numeric()) + } + + if (is.character(weights)) { + if (length(weights) != 1) { + cli::cli_abort( + "{.arg weights} as character must be a single edge attribute name.", + call = call + ) + } + if (!weights %in% edge_attr_names(graph)) { + cli::cli_abort("No such edge attribute", call = call) + } + return(edge_attr_as_weights(graph, weights, call)) + } + + if (!is.numeric(weights) && !is.logical(weights)) { + cli::cli_abort( + "{.arg weights} must be {.code NULL}, {.code NA}, a numeric vector, or an edge attribute name.", + call = call + ) + } + if (length(weights) != ecount(graph)) { + cli::cli_abort( + c( + "{.arg weights} must have length equal to the number of edges in the graph.", + i = "Expected length {ecount(graph)}, got {length(weights)}." + ), + call = call + ) + } + as.numeric(weights) +} + +edge_attr_as_weights <- function(graph, name, call) { + value <- edge_attr(graph, name) + if (!is.numeric(value) && !is.logical(value)) { + cli::cli_abort( + c( + "The {.val {name}} edge attribute must be numeric or logical.", + i = "Pass {.code weights = NA} to ignore it." + ), + call = call + ) + } + as.numeric(value) +} + +# Recover a legacy positional `attr` argument passed via `...`. +# +# Before 3.0.0, `attr` was the 3rd positional slot of as_adjacency_matrix() +# and as_biadjacency_matrix(). The new signatures insert `...` in that slot +# and move `weights`/`attr` after the dots, so calls like +# as_adjacency_matrix(g, "both", "weight") +# would otherwise hard-fail via rlang::check_dots_empty(). +# +# Dispatcher is dm-style: discriminate on whether dots are named or not +# (cynkra/dm R/filter-dm.R, dm_filter_api1()). Named extras stay an error +# (dots are reserved for future extension); a single unnamed character +# scalar is the legacy `attr` value and gets a soft deprecation. +# +# `user_env` is threaded through so `lifecycle::deprecate_soft()` recognises +# the caller's frame as user-facing (otherwise its is_direct() check sees +# the igraph namespace and silently swallows the warning). +recover_positional_attr <- function( + ..., + fn, + env = rlang::caller_env(), + user_env = rlang::caller_env() +) { + dots <- list(...) + if (length(dots) == 0L) { + return(NULL) + } + + nms <- rlang::names2(dots) + unnamed <- dots[!nzchar(nms)] + + if ( + length(dots) == 1L && + length(unnamed) == 1L && + is.character(unnamed[[1L]]) && + length(unnamed[[1L]]) == 1L + ) { + lifecycle::deprecate_soft( + "3.0.0", + sprintf("%s(attr = )", fn), + sprintf("%s(weights = )", fn), + details = "Pass the edge-attribute name by name (e.g. `weights = \"weight\"`).", + user_env = user_env + ) + return(unnamed[[1L]]) + } + + rlang::check_dots_empty(env = env) + NULL +} + get.adjacency.dense <- function( graph, type = c("both", "upper", "lower"), - attr = NULL, + weights = numeric(), loops = c("once", "twice", "ignore"), names = TRUE ) { @@ -247,8 +382,7 @@ get.adjacency.dense <- function( loops <- "none" } - if (is.null(attr)) { - # FIXME: Use get_adjacency_impl() also for non-NULL attr + if (length(weights) == 0) { res <- get_adjacency_impl( graph, type, @@ -260,9 +394,8 @@ get.adjacency.dense <- function( res <- as.matrix(get.adjacency.sparse( graph, type = type, - attr = attr, - names = names, - call = rlang::caller_env() + weights = weights, + names = names )) } @@ -275,31 +408,13 @@ get.adjacency.dense <- function( get.adjacency.sparse <- function( graph, type = c("both", "upper", "lower"), - attr = NULL, - names = TRUE, - call = rlang::caller_env() + weights = numeric(), + names = TRUE ) { ensure_igraph(graph) type <- igraph_match_arg(type) - # Prepare weights parameter - if (is.null(attr)) { - weights <- numeric() - } else { - attr <- as.character(attr) - if (!attr %in% edge_attr_names(graph)) { - cli::cli_abort("No such edge attribute", call = call) - } - weights <- edge_attr(graph, name = attr) - if (!is.numeric(weights) && !is.logical(weights)) { - cli::cli_abort( - "Matrices must be either numeric or logical, and the edge attribute is not", - call = call - ) - } - } - # Use the library implementation sparse_adjacency <- get_adjacency_sparse_impl( graph, @@ -334,18 +449,22 @@ get.adjacency.sparse <- function( #' right triangle of the matrix is used, `lower`: the lower left triangle #' of the matrix is used. `both`: the whole matrix is used, a symmetric #' matrix is returned. -#' @param attr Either `NULL` or a character string giving an edge -#' attribute name. If `NULL` a traditional adjacency matrix is returned. -#' If not `NULL` then the values of the given edge attribute are included -#' in the adjacency matrix. If the graph has multiple edges, the edge attribute -#' of an arbitrarily chosen edge (for the multiple edges) is included. This -#' argument is ignored if `edges` is `TRUE`. -#' -#' Note that this works only for certain attribute types. If the `sparse` -#' argumen is `TRUE`, then the attribute must be either logical or -#' numeric. If the `sparse` argument is `FALSE`, then character is -#' also allowed. The reason for the difference is that the `Matrix` -#' package does not support character sparse matrices yet. +#' @param ... These dots are for future extensions and must be empty. +#' @param weights One of the following: +#' \itemize{ +#' \item `NULL` (default): use the `weight` edge attribute if the graph has +#' one, otherwise return a traditional (unweighted) adjacency matrix. +#' \item `NA`: explicitly unweighted, ignoring any `weight` edge attribute. +#' \item A numeric or logical vector of length [ecount()]: use these values +#' directly as edge weights. +#' \item A character scalar: the name of an edge attribute whose values are +#' used as weights. The attribute must be numeric or logical. +#' } +#' If multiple edges share endpoints, the value of an arbitrarily chosen edge +#' is included in the matrix. +#' @param attr `r lifecycle::badge("deprecated")` Use `weights` instead. If +#' supplied, the value is forwarded to `weights` as a character edge +#' attribute name. #' @param edges `r lifecycle::badge("deprecated")` Logical scalar, whether to return the edge ids in the matrix. #' For non-existant edges zero is returned. #' @param names Logical constant, whether to assign row and column names @@ -365,30 +484,57 @@ get.adjacency.sparse <- function( #' V(g)$name <- letters[1:vcount(g)] #' as_adjacency_matrix(g) #' E(g)$weight <- runif(ecount(g)) -#' as_adjacency_matrix(g, attr = "weight") +#' as_adjacency_matrix(g) +#' as_adjacency_matrix(g, weights = NA) #' @family conversion #' @export as_adjacency_matrix <- function( graph, type = c("both", "upper", "lower"), - attr = NULL, + ..., + weights = NULL, + attr = deprecated(), edges = deprecated(), names = TRUE, sparse = igraph_opt("sparsematrices") ) { ensure_igraph(graph) + user_env <- rlang::caller_env() + recovered_attr <- recover_positional_attr( + ..., + fn = "as_adjacency_matrix", + user_env = user_env + ) + # Route into `weights` (not `attr`) so resolve_edge_weights() does not + # emit its own deprecation on top of the one we already issued. + if ( + !is.null(recovered_attr) && + !lifecycle::is_present(attr) && + is.null(weights) + ) { + weights <- recovered_attr + } + if (lifecycle::is_present(edges) && isTRUE(edges)) { lifecycle::deprecate_stop("2.0.0", "as_adjacency_matrix(edges = )") } + weights <- resolve_edge_weights( + graph, + weights, + attr, + fn = "as_adjacency_matrix", + user_env = user_env + ) + if (sparse) { - get.adjacency.sparse(graph, type = type, attr = attr, names = names) + get.adjacency.sparse(graph, type = type, weights = weights, names = names) } else { get.adjacency.dense( graph, type = type, - attr = attr, + weights = weights, names = names, loops = "once" ) @@ -407,7 +553,8 @@ as_adjacency_matrix <- function( as_adj <- function( graph, type = c("both", "upper", "lower"), - attr = NULL, + weights = NULL, + attr = deprecated(), edges = deprecated(), names = TRUE, sparse = igraph_opt("sparsematrices") @@ -417,6 +564,7 @@ as_adj <- function( as_adjacency_matrix( graph = graph, type = type, + weights = weights, attr = attr, edges = edges, names = names, @@ -916,10 +1064,9 @@ get.incidence.dense <- function( graph, types, names, - attr, - call = rlang::caller_env() + weights = numeric() ) { - if (is.null(attr)) { + if (length(weights) == 0) { ## Function call res <- get_biadjacency_impl( graph = graph, @@ -938,11 +1085,6 @@ get.incidence.dense <- function( types <- handle_vertex_type_arg(types, graph) - attr <- as.character(attr) - if (!attr %in% edge_attr_names(graph)) { - cli::cli_abort("No such edge attribute", call = call) - } - vc <- vcount(graph) n1 <- sum(!types) n2 <- vc - n1 @@ -964,14 +1106,7 @@ get.incidence.dense <- function( el[idx, ] <- el[idx, 2:1] # el[ ,1] only holds values 1..n1 and el[ ,2] values 1..n2 # and we can populate the matrix - value <- edge_attr(graph, attr) - if (!is.numeric(value) && !is.logical(value)) { - cli::cli_abort( - "Matrices must be either numeric or logical, and the edge attribute is not", - call = call - ) - } - res[el] <- value + res[el] <- weights if (names && "name" %in% vertex_attr_names(graph)) { rownames(res) <- V(graph)$name[which(!types)] @@ -988,7 +1123,7 @@ get.incidence.sparse <- function( graph, types, names, - attr, + weights = numeric(), call = rlang::caller_env() ) { types <- handle_vertex_type_arg(types, graph) @@ -1017,21 +1152,7 @@ get.incidence.sparse <- function( el[change, ] <- el[change, 2:1] el[, 2] <- el[, 2] - n1 - if (!is.null(attr)) { - attr <- as.character(attr) - if (!attr %in% edge_attr_names(graph)) { - cli::cli_abort("No such edge attribute", call = call) - } - value <- edge_attr(graph, name = attr) - if (!is.numeric(value) && !is.logical(value)) { - cli::cli_abort( - "Matrices must be either numeric or logical, and the edge attribute is not", - call = call - ) - } - } else { - value <- rep(1, nrow(el)) - } + value <- if (length(weights) == 0) rep(1, nrow(el)) else weights res <- Matrix::spMatrix(n1, n2, i = el[, 1], j = el[, 2], x = value) @@ -1061,12 +1182,7 @@ get.incidence.sparse <- function( #' @param types An optional vertex type vector to use instead of the #' `type` vertex attribute. You must supply this argument if the graph has #' no `type` vertex attribute. -#' @param attr Either `NULL` or a character string giving an edge -#' attribute name. If `NULL`, then a traditional bipartite adjacency matrix is -#' returned. If not `NULL` then the values of the given edge attribute are -#' included in the bipartite adjacency matrix. If the graph has multiple edges, the edge -#' attribute of an arbitrarily chosen edge (for the multiple edges) is -#' included. +#' @inheritParams as_adjacency_matrix #' @param names Logical scalar, if `TRUE` and the vertices in the graph #' are named (i.e. the graph has a vertex attribute called `name`), then #' vertex names will be added to the result as row and column names. Otherwise @@ -1091,26 +1207,50 @@ get.incidence.sparse <- function( as_biadjacency_matrix <- function( graph, types = NULL, - attr = NULL, + ..., + weights = NULL, + attr = deprecated(), names = TRUE, sparse = FALSE ) { # Argument checks ensure_igraph(graph) + user_env <- rlang::caller_env() + recovered_attr <- recover_positional_attr( + ..., + fn = "as_biadjacency_matrix", + user_env = user_env + ) + if ( + !is.null(recovered_attr) && + !lifecycle::is_present(attr) && + is.null(weights) + ) { + weights <- recovered_attr + } + names <- as.logical(names) sparse <- as.logical(sparse) + weights <- resolve_edge_weights( + graph, + weights, + attr, + fn = "as_biadjacency_matrix", + user_env = user_env + ) + if (sparse) { get.incidence.sparse( graph, types = types, names = names, - attr = attr, + weights = weights, call = rlang::caller_env() ) } else { - get.incidence.dense(graph, types = types, names = names, attr = attr) + get.incidence.dense(graph, types = types, names = names, weights = weights) } } #' As incidence matrix diff --git a/R/indexing.R b/R/indexing.R index 72b83686ea6..17654001b05 100644 --- a/R/indexing.R +++ b/R/indexing.R @@ -266,7 +266,11 @@ get_adjacency_submatrix <- function(x, i, j, attr = NULL) { } if (missing(i) && missing(j)) { - return(as_adjacency_matrix(x, sparse = sparse, attr = attr)) + return(as_adjacency_matrix( + x, + sparse = sparse, + weights = if (is.null(attr)) NA else attr + )) } # convert logical, character or negative i/j to proper vertex ids diff --git a/man/alpha.centrality.Rd b/man/alpha.centrality.Rd index 06576dd5f4b..c12796e29aa 100644 --- a/man/alpha.centrality.Rd +++ b/man/alpha.centrality.Rd @@ -35,11 +35,18 @@ the same factor for every node, or a vector giving the factor for every vertex. Note that too long vectors will be truncated and too short vectors will be replicated to match the number of vertices.} -\item{weights}{A character scalar that gives the name of the edge attribute -to use in the adjacency matrix. If it is \code{NULL}, then the -\sQuote{weight} edge attribute of the graph is used, if there is one. -Otherwise, or if it is \code{NA}, then the calculation uses the standard -adjacency matrix.} +\item{weights}{One of the following: +\itemize{ +\item \code{NULL} (default): use the \code{weight} edge attribute if the graph has +one, otherwise return a traditional (unweighted) adjacency matrix. +\item \code{NA}: explicitly unweighted, ignoring any \code{weight} edge attribute. +\item A numeric or logical vector of length \code{\link[=ecount]{ecount()}}: use these values +directly as edge weights. +\item A character scalar: the name of an edge attribute whose values are +used as weights. The attribute must be numeric or logical. +} +If multiple edges share endpoints, the value of an arbitrarily chosen edge +is included in the matrix.} \item{tol}{Tolerance for near-singularities during matrix inversion, see \code{\link[=solve]{solve()}}.} diff --git a/man/alpha_centrality.Rd b/man/alpha_centrality.Rd index f9c415130a1..b647c56e437 100644 --- a/man/alpha_centrality.Rd +++ b/man/alpha_centrality.Rd @@ -35,11 +35,18 @@ the same factor for every node, or a vector giving the factor for every vertex. Note that too long vectors will be truncated and too short vectors will be replicated to match the number of vertices.} -\item{weights}{A character scalar that gives the name of the edge attribute -to use in the adjacency matrix. If it is \code{NULL}, then the -\sQuote{weight} edge attribute of the graph is used, if there is one. -Otherwise, or if it is \code{NA}, then the calculation uses the standard -adjacency matrix.} +\item{weights}{One of the following: +\itemize{ +\item \code{NULL} (default): use the \code{weight} edge attribute if the graph has +one, otherwise return a traditional (unweighted) adjacency matrix. +\item \code{NA}: explicitly unweighted, ignoring any \code{weight} edge attribute. +\item A numeric or logical vector of length \code{\link[=ecount]{ecount()}}: use these values +directly as edge weights. +\item A character scalar: the name of an edge attribute whose values are +used as weights. The attribute must be numeric or logical. +} +If multiple edges share endpoints, the value of an arbitrarily chosen edge +is included in the matrix.} \item{tol}{Tolerance for near-singularities during matrix inversion, see \code{\link[=solve]{solve()}}.} diff --git a/man/as_adj.Rd b/man/as_adj.Rd index 61f797bb585..c1e6fb033de 100644 --- a/man/as_adj.Rd +++ b/man/as_adj.Rd @@ -7,7 +7,8 @@ as_adj( graph, type = c("both", "upper", "lower"), - attr = NULL, + weights = NULL, + attr = deprecated(), edges = deprecated(), names = TRUE, sparse = igraph_opt("sparsematrices") @@ -22,18 +23,22 @@ right triangle of the matrix is used, \code{lower}: the lower left triangle of the matrix is used. \code{both}: the whole matrix is used, a symmetric matrix is returned.} -\item{attr}{Either \code{NULL} or a character string giving an edge -attribute name. If \code{NULL} a traditional adjacency matrix is returned. -If not \code{NULL} then the values of the given edge attribute are included -in the adjacency matrix. If the graph has multiple edges, the edge attribute -of an arbitrarily chosen edge (for the multiple edges) is included. This -argument is ignored if \code{edges} is \code{TRUE}. +\item{weights}{One of the following: +\itemize{ +\item \code{NULL} (default): use the \code{weight} edge attribute if the graph has +one, otherwise return a traditional (unweighted) adjacency matrix. +\item \code{NA}: explicitly unweighted, ignoring any \code{weight} edge attribute. +\item A numeric or logical vector of length \code{\link[=ecount]{ecount()}}: use these values +directly as edge weights. +\item A character scalar: the name of an edge attribute whose values are +used as weights. The attribute must be numeric or logical. +} +If multiple edges share endpoints, the value of an arbitrarily chosen edge +is included in the matrix.} -Note that this works only for certain attribute types. If the \code{sparse} -argumen is \code{TRUE}, then the attribute must be either logical or -numeric. If the \code{sparse} argument is \code{FALSE}, then character is -also allowed. The reason for the difference is that the \code{Matrix} -package does not support character sparse matrices yet.} +\item{attr}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weights} instead. If +supplied, the value is forwarded to \code{weights} as a character edge +attribute name.} \item{edges}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Logical scalar, whether to return the edge ids in the matrix. For non-existant edges zero is returned.} diff --git a/man/as_adjacency_matrix.Rd b/man/as_adjacency_matrix.Rd index ee5b1ca1cd0..8e0a6712571 100644 --- a/man/as_adjacency_matrix.Rd +++ b/man/as_adjacency_matrix.Rd @@ -7,7 +7,9 @@ as_adjacency_matrix( graph, type = c("both", "upper", "lower"), - attr = NULL, + ..., + weights = NULL, + attr = deprecated(), edges = deprecated(), names = TRUE, sparse = igraph_opt("sparsematrices") @@ -22,18 +24,24 @@ right triangle of the matrix is used, \code{lower}: the lower left triangle of the matrix is used. \code{both}: the whole matrix is used, a symmetric matrix is returned.} -\item{attr}{Either \code{NULL} or a character string giving an edge -attribute name. If \code{NULL} a traditional adjacency matrix is returned. -If not \code{NULL} then the values of the given edge attribute are included -in the adjacency matrix. If the graph has multiple edges, the edge attribute -of an arbitrarily chosen edge (for the multiple edges) is included. This -argument is ignored if \code{edges} is \code{TRUE}. +\item{...}{These dots are for future extensions and must be empty.} -Note that this works only for certain attribute types. If the \code{sparse} -argumen is \code{TRUE}, then the attribute must be either logical or -numeric. If the \code{sparse} argument is \code{FALSE}, then character is -also allowed. The reason for the difference is that the \code{Matrix} -package does not support character sparse matrices yet.} +\item{weights}{One of the following: +\itemize{ +\item \code{NULL} (default): use the \code{weight} edge attribute if the graph has +one, otherwise return a traditional (unweighted) adjacency matrix. +\item \code{NA}: explicitly unweighted, ignoring any \code{weight} edge attribute. +\item A numeric or logical vector of length \code{\link[=ecount]{ecount()}}: use these values +directly as edge weights. +\item A character scalar: the name of an edge attribute whose values are +used as weights. The attribute must be numeric or logical. +} +If multiple edges share endpoints, the value of an arbitrarily chosen edge +is included in the matrix.} + +\item{attr}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weights} instead. If +supplied, the value is forwarded to \code{weights} as a character edge +attribute name.} \item{edges}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Logical scalar, whether to return the edge ids in the matrix. For non-existant edges zero is returned.} @@ -71,7 +79,8 @@ as_adjacency_matrix(g) V(g)$name <- letters[1:vcount(g)] as_adjacency_matrix(g) E(g)$weight <- runif(ecount(g)) -as_adjacency_matrix(g, attr = "weight") +as_adjacency_matrix(g) +as_adjacency_matrix(g, weights = NA) } \seealso{ \code{\link[=graph_from_adjacency_matrix]{graph_from_adjacency_matrix()}}, \code{\link[=read_graph]{read_graph()}} diff --git a/man/as_biadjacency_matrix.Rd b/man/as_biadjacency_matrix.Rd index ee9b545edb1..755e6a54587 100644 --- a/man/as_biadjacency_matrix.Rd +++ b/man/as_biadjacency_matrix.Rd @@ -7,7 +7,9 @@ as_biadjacency_matrix( graph, types = NULL, - attr = NULL, + ..., + weights = NULL, + attr = deprecated(), names = TRUE, sparse = FALSE ) @@ -20,12 +22,24 @@ directed graphs.} \code{type} vertex attribute. You must supply this argument if the graph has no \code{type} vertex attribute.} -\item{attr}{Either \code{NULL} or a character string giving an edge -attribute name. If \code{NULL}, then a traditional bipartite adjacency matrix is -returned. If not \code{NULL} then the values of the given edge attribute are -included in the bipartite adjacency matrix. If the graph has multiple edges, the edge -attribute of an arbitrarily chosen edge (for the multiple edges) is -included.} +\item{...}{These dots are for future extensions and must be empty.} + +\item{weights}{One of the following: +\itemize{ +\item \code{NULL} (default): use the \code{weight} edge attribute if the graph has +one, otherwise return a traditional (unweighted) adjacency matrix. +\item \code{NA}: explicitly unweighted, ignoring any \code{weight} edge attribute. +\item A numeric or logical vector of length \code{\link[=ecount]{ecount()}}: use these values +directly as edge weights. +\item A character scalar: the name of an edge attribute whose values are +used as weights. The attribute must be numeric or logical. +} +If multiple edges share endpoints, the value of an arbitrarily chosen edge +is included in the matrix.} + +\item{attr}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weights} instead. If +supplied, the value is forwarded to \code{weights} as a character edge +attribute name.} \item{names}{Logical scalar, if \code{TRUE} and the vertices in the graph are named (i.e. the graph has a vertex attribute called \code{name}), then diff --git a/man/as_incidence_matrix.Rd b/man/as_incidence_matrix.Rd index 90f90e19848..88d1c436f48 100644 --- a/man/as_incidence_matrix.Rd +++ b/man/as_incidence_matrix.Rd @@ -6,6 +6,9 @@ \usage{ as_incidence_matrix(...) } +\arguments{ +\item{...}{These dots are for future extensions and must be empty.} +} \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} diff --git a/man/get.adjacency.Rd b/man/get.adjacency.Rd index fcab543222c..aa40b031c0d 100644 --- a/man/get.adjacency.Rd +++ b/man/get.adjacency.Rd @@ -22,18 +22,9 @@ right triangle of the matrix is used, \code{lower}: the lower left triangle of the matrix is used. \code{both}: the whole matrix is used, a symmetric matrix is returned.} -\item{attr}{Either \code{NULL} or a character string giving an edge -attribute name. If \code{NULL} a traditional adjacency matrix is returned. -If not \code{NULL} then the values of the given edge attribute are included -in the adjacency matrix. If the graph has multiple edges, the edge attribute -of an arbitrarily chosen edge (for the multiple edges) is included. This -argument is ignored if \code{edges} is \code{TRUE}. - -Note that this works only for certain attribute types. If the \code{sparse} -argumen is \code{TRUE}, then the attribute must be either logical or -numeric. If the \code{sparse} argument is \code{FALSE}, then character is -also allowed. The reason for the difference is that the \code{Matrix} -package does not support character sparse matrices yet.} +\item{attr}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weights} instead. If +supplied, the value is forwarded to \code{weights} as a character edge +attribute name.} \item{edges}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Logical scalar, whether to return the edge ids in the matrix. For non-existant edges zero is returned.} diff --git a/man/get.incidence.Rd b/man/get.incidence.Rd index b4f40697fb6..fc14a4248be 100644 --- a/man/get.incidence.Rd +++ b/man/get.incidence.Rd @@ -14,12 +14,9 @@ directed graphs.} \code{type} vertex attribute. You must supply this argument if the graph has no \code{type} vertex attribute.} -\item{attr}{Either \code{NULL} or a character string giving an edge -attribute name. If \code{NULL}, then a traditional bipartite adjacency matrix is -returned. If not \code{NULL} then the values of the given edge attribute are -included in the bipartite adjacency matrix. If the graph has multiple edges, the edge -attribute of an arbitrarily chosen edge (for the multiple edges) is -included.} +\item{attr}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Use \code{weights} instead. If +supplied, the value is forwarded to \code{weights} as a character edge +attribute name.} \item{names}{Logical scalar, if \code{TRUE} and the vertices in the graph are named (i.e. the graph has a vertex attribute called \code{name}), then diff --git a/man/power_centrality.Rd b/man/power_centrality.Rd index bce0108e8ab..1a82a203d75 100644 --- a/man/power_centrality.Rd +++ b/man/power_centrality.Rd @@ -11,7 +11,8 @@ power_centrality( exponent = 1, rescale = FALSE, tol = 1e-07, - sparse = TRUE + sparse = TRUE, + weights = NULL ) } \arguments{ @@ -36,6 +37,19 @@ score; can be negative} \item{sparse}{Logical scalar, whether to use sparse matrices for the calculation. The \sQuote{Matrix} package is required for sparse matrix support} + +\item{weights}{One of the following: +\itemize{ +\item \code{NULL} (default): use the \code{weight} edge attribute if the graph has +one, otherwise return a traditional (unweighted) adjacency matrix. +\item \code{NA}: explicitly unweighted, ignoring any \code{weight} edge attribute. +\item A numeric or logical vector of length \code{\link[=ecount]{ecount()}}: use these values +directly as edge weights. +\item A character scalar: the name of an edge attribute whose values are +used as weights. The attribute must be numeric or logical. +} +If multiple edges share endpoints, the value of an arbitrarily chosen edge +is included in the matrix.} } \value{ A vector, containing the centrality scores. @@ -117,19 +131,19 @@ g.f <- make_graph( dir = FALSE ) # Compute power centrality scores -for (e in seq(-0.5, .5, by = 0.1)) { +for (e in seq(-0.5, 0.5, by = 0.1)) { print(round(power_centrality(g.c, exp = e)[c(1, 2, 4)], 2)) } -for (e in seq(-0.4, .4, by = 0.1)) { +for (e in seq(-0.4, 0.4, by = 0.1)) { print(round(power_centrality(g.d, exp = e)[c(1, 2, 5)], 2)) } -for (e in seq(-0.4, .4, by = 0.1)) { +for (e in seq(-0.4, 0.4, by = 0.1)) { print(round(power_centrality(g.e, exp = e)[c(1, 2, 5)], 2)) } -for (e in seq(-0.4, .4, by = 0.1)) { +for (e in seq(-0.4, 0.4, by = 0.1)) { print(round(power_centrality(g.f, exp = e)[c(1, 2, 5)], 2)) } diff --git a/tests/testthat/_snaps/conversion.md b/tests/testthat/_snaps/conversion.md index a7afcea5935..9a002c6334e 100644 --- a/tests/testthat/_snaps/conversion.md +++ b/tests/testthat/_snaps/conversion.md @@ -23,7 +23,7 @@ # as_adjacency_matrix() errors well -- sparse Code - as_adjacency_matrix(g, attr = "bla") + as_adjacency_matrix(g, weights = "bla") Condition Error in `as_adjacency_matrix()`: ! No such edge attribute @@ -31,15 +31,16 @@ --- Code - as_adjacency_matrix(g, attr = "bla") + as_adjacency_matrix(g, weights = "bla") Condition Error in `as_adjacency_matrix()`: - ! Matrices must be either numeric or logical, and the edge attribute is not + ! The "bla" edge attribute must be numeric or logical. + i Pass `weights = NA` to ignore it. # as_adjacency_matrix() errors well -- dense Code - as_adjacency_matrix(g, attr = "bla", sparse = FALSE) + as_adjacency_matrix(g, weights = "bla", sparse = FALSE) Condition Error in `as_adjacency_matrix()`: ! No such edge attribute @@ -47,10 +48,75 @@ --- Code - as_adjacency_matrix(g, attr = "bla", sparse = FALSE) + as_adjacency_matrix(g, weights = "bla", sparse = FALSE) Condition Error in `as_adjacency_matrix()`: - ! Matrices must be either numeric or logical, and the edge attribute is not + ! The "bla" edge attribute must be numeric or logical. + i Pass `weights = NA` to ignore it. + +# as_adjacency_matrix() errors on wrong-length weights vector + + Code + as_adjacency_matrix(g, weights = c(1, 2, 3), sparse = FALSE) + Condition + Error in `as_adjacency_matrix()`: + ! `weights` must have length equal to the number of edges in the graph. + i Expected length 6, got 3. + +--- + + Code + as_adjacency_matrix(g, weights = c(1, 2, 3), sparse = TRUE) + Condition + Error in `as_adjacency_matrix()`: + ! `weights` must have length equal to the number of edges in the graph. + i Expected length 6, got 3. + +# as_adjacency_matrix() errors on non-numeric weights + + Code + as_adjacency_matrix(g, weights = list(1, 2, 3, 4, 5, 6)) + Condition + Error in `as_adjacency_matrix()`: + ! `weights` must be `NULL`, `NA`, a numeric vector, or an edge attribute name. + +# as_adjacency_matrix(attr =) is deprecated but still works + + Code + A <- as_adjacency_matrix(g, attr = "weight", sparse = FALSE) + Condition + Warning: + The `attr` argument of `as_adjacency_matrix()` is deprecated as of igraph 3.0.0. + i Please use the `weights` argument instead. + +# as_adjacency_matrix() recovers legacy positional `attr` + + Code + A <- as_adjacency_matrix(g, "both", "weight", sparse = FALSE) + Condition + Warning: + The `attr` argument of `as_adjacency_matrix()` is deprecated as of igraph 3.0.0. + i Please use the `weights` argument instead. + i Pass the edge-attribute name by name (e.g. `weights = "weight"`). + +# as_biadjacency_matrix(attr =) is deprecated but still works + + Code + A <- as_biadjacency_matrix(g, attr = "weight", sparse = FALSE) + Condition + Warning: + The `attr` argument of `as_biadjacency_matrix()` is deprecated as of igraph 3.0.0. + i Please use the `weights` argument instead. + +# as_biadjacency_matrix() recovers legacy positional `attr` + + Code + A <- as_biadjacency_matrix(g, NULL, "weight", sparse = FALSE) + Condition + Warning: + The `attr` argument of `as_biadjacency_matrix()` is deprecated as of igraph 3.0.0. + i Please use the `weights` argument instead. + i Pass the edge-attribute name by name (e.g. `weights = "weight"`). # as_long_data_frame() works correctly with and without names @@ -161,7 +227,7 @@ --- Code - as_adjacency_matrix(g_dir_wt, attr = "weight", sparse = TRUE) + as_adjacency_matrix(g_dir_wt, weights = "weight", sparse = TRUE) Output 3 x 3 sparse Matrix of class "dgCMatrix" @@ -172,7 +238,7 @@ --- Code - as_adjacency_matrix(g_dir_wt, attr = "weight", type = "upper", sparse = TRUE) + as_adjacency_matrix(g_dir_wt, weights = "weight", type = "upper", sparse = TRUE) Output 3 x 3 sparse Matrix of class "dgCMatrix" @@ -183,7 +249,7 @@ --- Code - as_adjacency_matrix(g_dir_wt, attr = "weight", type = "lower", sparse = TRUE) + as_adjacency_matrix(g_dir_wt, weights = "weight", type = "lower", sparse = TRUE) Output 3 x 3 sparse Matrix of class "dgCMatrix" @@ -194,7 +260,7 @@ --- Code - as_adjacency_matrix(g_dir_wt, attr = "weight", sparse = FALSE) + as_adjacency_matrix(g_dir_wt, weights = "weight", sparse = FALSE) Output [,1] [,2] [,3] [1,] 0.0 1.5 0.0 @@ -204,7 +270,7 @@ --- Code - as_adjacency_matrix(g_dir_wt, attr = "weight", type = "upper", sparse = FALSE) + as_adjacency_matrix(g_dir_wt, weights = "weight", type = "upper", sparse = FALSE) Output [,1] [,2] [,3] [1,] 0.0 1.5 0.0 @@ -214,7 +280,7 @@ --- Code - as_adjacency_matrix(g_dir_wt, attr = "weight", type = "lower", sparse = FALSE) + as_adjacency_matrix(g_dir_wt, weights = "weight", type = "lower", sparse = FALSE) Output [,1] [,2] [,3] [1,] 0.0 1.5 0.0 @@ -308,7 +374,7 @@ --- Code - as_adjacency_matrix(g_undir_wt, attr = "weight", sparse = TRUE) + as_adjacency_matrix(g_undir_wt, weights = "weight", sparse = TRUE) Output 3 x 3 sparse Matrix of class "dgCMatrix" @@ -319,7 +385,7 @@ --- Code - as_adjacency_matrix(g_undir_wt, attr = "weight", type = "upper", sparse = TRUE) + as_adjacency_matrix(g_undir_wt, weights = "weight", type = "upper", sparse = TRUE) Output 3 x 3 sparse Matrix of class "dgCMatrix" @@ -330,7 +396,7 @@ --- Code - as_adjacency_matrix(g_undir_wt, attr = "weight", type = "lower", sparse = TRUE) + as_adjacency_matrix(g_undir_wt, weights = "weight", type = "lower", sparse = TRUE) Output 3 x 3 sparse Matrix of class "dgCMatrix" @@ -341,7 +407,7 @@ --- Code - as_adjacency_matrix(g_undir_wt, attr = "weight", type = "both", sparse = TRUE) + as_adjacency_matrix(g_undir_wt, weights = "weight", type = "both", sparse = TRUE) Output 3 x 3 sparse Matrix of class "dgCMatrix" @@ -352,7 +418,7 @@ --- Code - as_adjacency_matrix(g_undir_wt, attr = "weight", sparse = FALSE) + as_adjacency_matrix(g_undir_wt, weights = "weight", sparse = FALSE) Output [,1] [,2] [,3] [1,] 0.0 2.1 3.2 @@ -362,7 +428,7 @@ --- Code - as_adjacency_matrix(g_undir_wt, attr = "weight", type = "upper", sparse = FALSE) + as_adjacency_matrix(g_undir_wt, weights = "weight", type = "upper", sparse = FALSE) Output [,1] [,2] [,3] [1,] 0 2.1 3.2 @@ -372,7 +438,7 @@ --- Code - as_adjacency_matrix(g_undir_wt, attr = "weight", type = "lower", sparse = FALSE) + as_adjacency_matrix(g_undir_wt, weights = "weight", type = "lower", sparse = FALSE) Output [,1] [,2] [,3] [1,] 0.0 0.0 0 @@ -382,7 +448,7 @@ --- Code - as_adjacency_matrix(g_undir_wt, attr = "weight", type = "both", sparse = FALSE) + as_adjacency_matrix(g_undir_wt, weights = "weight", type = "both", sparse = FALSE) Output [,1] [,2] [,3] [1,] 0.0 2.1 3.2 diff --git a/tests/testthat/test-centrality.R b/tests/testthat/test-centrality.R index 9602998d12e..7529da33376 100644 --- a/tests/testthat/test-centrality.R +++ b/tests/testthat/test-centrality.R @@ -674,6 +674,69 @@ test_that("alpha_centrality() works with custom weight attribute names", { expect_equal(ac_sparse, ac_dense) }) +test_that("alpha_centrality() accepts a numeric weights vector", { + star <- make_star(10) + w <- sample(ecount(star)) + E(star)$weight <- w + + ac_attr <- alpha_centrality(star, weights = "weight", sparse = FALSE) + ac_vec <- alpha_centrality(star, weights = w, sparse = FALSE) + expect_equal(ac_vec, ac_attr) + + ac_attr_sp <- alpha_centrality(star, weights = "weight", sparse = TRUE) + ac_vec_sp <- alpha_centrality(star, weights = w, sparse = TRUE) + expect_equal(ac_vec_sp, ac_attr_sp) +}) + +test_that("alpha_centrality() does not mutate the input graph", { + star <- make_star(10) + w <- sample(ecount(star)) + alpha_centrality(star, weights = w, sparse = FALSE) + expect_false("weight" %in% edge_attr_names(star)) + alpha_centrality(star, weights = w, sparse = TRUE) + expect_false("weight" %in% edge_attr_names(star)) +}) + +test_that("power_centrality() supports edge weights, #903", { + # A weighted graph and an equivalent graph with parallel edges in place of + # weights should give the same power_centrality result when the unweighted + # variant has the equivalent multi-edge structure. + incidence <- matrix(c(1, 0, 1, 0, 1, 1, 1, 1, 1), nrow = 3, ncol = 3) + bipartite <- graph_from_biadjacency_matrix(incidence) + gph_weighted <- bipartite_projection(bipartite, which = TRUE) + adj_weighted <- as_adjacency_matrix(gph_weighted, weights = "weight") + gph_unweighted <- graph_from_adjacency_matrix(adj_weighted) + + expect_equal( + power_centrality(gph_weighted, sparse = FALSE), + power_centrality(gph_unweighted, sparse = FALSE) + ) + expect_equal( + power_centrality(gph_weighted, sparse = TRUE), + power_centrality(gph_unweighted, sparse = TRUE) + ) +}) + +test_that("power_centrality(weights = NA) ignores `weight` attribute", { + g <- make_ring(5, directed = FALSE) + E(g)$weight <- c(1, 2, 3, 4, 5) + unwt <- make_ring(5, directed = FALSE) + expect_equal( + power_centrality(g, weights = NA, sparse = FALSE), + power_centrality(unwt, sparse = FALSE) + ) +}) + +test_that("power_centrality() accepts a numeric weights vector", { + g <- make_ring(5, directed = FALSE) + w <- c(1.5, 2.5, 3.5, 4.5, 5.5) + E(g)$weight <- w + expect_equal( + power_centrality(g, weights = w, sparse = FALSE), + power_centrality(g, sparse = FALSE) + ) +}) + test_that("undirected alpha_centrality() works, #653", { g <- make_ring(10) diff --git a/tests/testthat/test-conversion.R b/tests/testthat/test-conversion.R index a497f9e4f82..fd1e1acb30f 100644 --- a/tests/testthat/test-conversion.R +++ b/tests/testthat/test-conversion.R @@ -104,7 +104,7 @@ test_that("as_adjacency_matrix() works -- sparse", { expect_equal(basic_adj_matrix_dense, letter_adj_matrix_dense) E(g)$weight <- c(1.2, 3.4, 2.7, 5.6, 6.0, 0.1, 6.1, 3.3, 4.3) - weight_adj_matrix <- as_adjacency_matrix(g, attr = "weight") + weight_adj_matrix <- as_adjacency_matrix(g, weights = "weight") expect_s4_class(weight_adj_matrix, "dgCMatrix") expect_equal( as.matrix(weight_adj_matrix), @@ -142,10 +142,10 @@ test_that("as_adjacency_matrix() works -- sparse + not both", { test_that("as_adjacency_matrix() errors well -- sparse", { g <- make_graph(c(1, 2, 2, 1, 2, 2, 3, 3, 3, 3, 3, 4, 4, 2, 4, 2, 4, 2), directed = TRUE) - expect_snapshot_igraph_error(as_adjacency_matrix(g, attr = "bla")) + expect_snapshot_igraph_error(as_adjacency_matrix(g, weights = "bla")) E(g)$bla <- letters[1:ecount(g)] - expect_snapshot_igraph_error(as_adjacency_matrix(g, attr = "bla")) + expect_snapshot_igraph_error(as_adjacency_matrix(g, weights = "bla")) }) test_that("as_adjacency_matrix() works -- sparse undirected", { @@ -183,7 +183,11 @@ test_that("as_adjacency_matrix() works -- dense", { expect_equal(basic_adj_matrix, unname(letter_adj_matrix)) E(g)$weight <- c(1.2, 3.4, 2.7, 5.6, 6.0, 0.1, 6.1, 3.3, 4.3) - weight_adj_matrix <- as_adjacency_matrix(g, attr = "weight", sparse = FALSE) + weight_adj_matrix <- as_adjacency_matrix( + g, + weights = "weight", + sparse = FALSE + ) expect_equal( weight_adj_matrix, matrix( @@ -198,12 +202,12 @@ test_that("as_adjacency_matrix() works -- dense", { test_that("as_adjacency_matrix() errors well -- dense", { g <- make_graph(c(1, 2, 2, 1, 2, 2, 3, 3, 3, 3, 3, 4, 4, 2, 4, 2, 4, 2), directed = TRUE) expect_snapshot_igraph_error( - as_adjacency_matrix(g, attr = "bla", sparse = FALSE) + as_adjacency_matrix(g, weights = "bla", sparse = FALSE) ) E(g)$bla <- letters[1:ecount(g)] expect_snapshot_igraph_error( - as_adjacency_matrix(g, attr = "bla", sparse = FALSE) + as_adjacency_matrix(g, weights = "bla", sparse = FALSE) ) }) @@ -222,7 +226,11 @@ test_that("as_adjacency_matrix() works -- dense undirected", { ) E(ug)$weight <- c(1.2, 3.4, 2.7, 5.6, 6.0, 0.1, 6.1, 3.3, 4.3) - weight_adj_matrix <- as_adjacency_matrix(ug, sparse = FALSE, attr = "weight") + weight_adj_matrix <- as_adjacency_matrix( + ug, + sparse = FALSE, + weights = "weight" + ) dimnames(weight_adj_matrix) <- NULL expect_equal( weight_adj_matrix, @@ -243,7 +251,7 @@ test_that("as_adjacency_matrix() works -- dense + not both", { g, type = "lower", sparse = FALSE, - attr = "attribute" + weights = "attribute" ) dimnames(lower_adj_matrix) <- NULL @@ -260,7 +268,7 @@ test_that("as_adjacency_matrix() works -- dense + not both", { g, type = "upper", sparse = FALSE, - attr = "attribute" + weights = "attribute" ) dimnames(upper_adj_matrix) <- NULL expect_equal( @@ -279,14 +287,14 @@ test_that("as_adjacency_matrix() works -- dense + weights", { mat <- matrix(0, 5, 5) mat[lower.tri(mat)] <- 1:10 mat <- mat + t(mat) - A <- as_adjacency_matrix(g, attr = "weight", sparse = FALSE) + A <- as_adjacency_matrix(g, weights = "weight", sparse = FALSE) expect_equal(as_unnamed_dense_matrix(A), mat) }) test_that("as_biadjacency_matrix() works -- dense + weights", { g <- make_bipartite_graph(c(0, 1, 0, 1, 0, 0), c(1, 2, 2, 3, 3, 4)) E(g)$weight <- c(2, 4, 6) - A <- as_biadjacency_matrix(g, attr = "weight", sparse = FALSE) + A <- as_biadjacency_matrix(g, weights = "weight", sparse = FALSE) mat <- matrix( c(2, 4, 0, 0, 0, 6, 0, 0), nrow = 4L, @@ -296,6 +304,167 @@ test_that("as_biadjacency_matrix() works -- dense + weights", { expect_equal(as_unnamed_dense_matrix(A), as_unnamed_dense_matrix(mat)) }) +# --- weights parameter: the four input modes ------------------------------- + +test_that("as_adjacency_matrix() picks up `weight` attribute by default", { + g <- make_full_graph(4, directed = FALSE) + E(g)$weight <- c(1, 2, 3, 4, 5, 6) + expected <- as_adjacency_matrix(g, weights = "weight", sparse = FALSE) + expect_equal(as_adjacency_matrix(g, sparse = FALSE), expected) + expect_equal( + as.matrix(as_adjacency_matrix(g, sparse = TRUE)), + as.matrix(expected) + ) +}) + +test_that("as_adjacency_matrix() weights = NA returns unweighted matrix", { + g <- make_full_graph(4, directed = FALSE) + E(g)$weight <- c(1, 2, 3, 4, 5, 6) + plain <- matrix(1, 4, 4) + diag(plain) <- 0 + expect_equal( + as_adjacency_matrix(g, weights = NA, sparse = FALSE), + plain + ) + expect_equal( + as.matrix(as_adjacency_matrix(g, weights = NA, sparse = TRUE)), + plain + ) +}) + +test_that("as_adjacency_matrix() accepts a numeric weights vector", { + g <- make_full_graph(4, directed = FALSE) + w <- c(10, 20, 30, 40, 50, 60) + A_dense <- as_adjacency_matrix(g, weights = w, sparse = FALSE) + A_sparse <- as_adjacency_matrix(g, weights = w, sparse = TRUE) + expect_equal(A_dense, as.matrix(A_sparse)) + # the weights should appear somewhere in the matrix + expect_setequal(setdiff(unique(as.vector(A_dense)), 0), w) +}) + +test_that("as_adjacency_matrix() weights = character names an edge attribute", { + g <- make_full_graph(4, directed = FALSE) + E(g)$myweight <- c(7, 8, 9, 10, 11, 12) + expect_equal( + as_adjacency_matrix(g, weights = "myweight", sparse = FALSE), + as_adjacency_matrix(g, weights = c(7, 8, 9, 10, 11, 12), sparse = FALSE) + ) +}) + +test_that("as_adjacency_matrix() errors on wrong-length weights vector", { + g <- make_full_graph(4, directed = FALSE) + expect_snapshot_igraph_error( + as_adjacency_matrix(g, weights = c(1, 2, 3), sparse = FALSE) + ) + expect_snapshot_igraph_error( + as_adjacency_matrix(g, weights = c(1, 2, 3), sparse = TRUE) + ) +}) + +test_that("as_adjacency_matrix() errors on non-numeric weights", { + g <- make_full_graph(4, directed = FALSE) + expect_snapshot_igraph_error( + as_adjacency_matrix(g, weights = list(1, 2, 3, 4, 5, 6)) + ) +}) + +test_that("as_adjacency_matrix(attr =) is deprecated but still works", { + g <- make_full_graph(4, directed = FALSE) + E(g)$weight <- 1:6 + expected <- as_adjacency_matrix(g, weights = "weight", sparse = FALSE) + expect_snapshot( + A <- as_adjacency_matrix(g, attr = "weight", sparse = FALSE) + ) + expect_equal(A, expected) +}) + +test_that("as_adjacency_matrix() recovers legacy positional `attr`", { + g <- make_full_graph(4, directed = FALSE) + E(g)$weight <- 1:6 + expected <- as_adjacency_matrix(g, weights = "weight", sparse = FALSE) + expect_snapshot( + A <- as_adjacency_matrix(g, "both", "weight", sparse = FALSE) + ) + expect_equal(A, expected) +}) + +test_that("as_adjacency_matrix() keeps hard errors for unrecoverable dots", { + g <- make_full_graph(4, directed = FALSE) + # Numeric in `...` is not a legacy `attr` value (always character) -> error. + expect_error(as_adjacency_matrix(g, "both", 1:6)) + # Multiple unnamed positionals -> error. + expect_error(as_adjacency_matrix(g, "both", "weight", "extra")) + # Unknown named extra -> error. + expect_error(as_adjacency_matrix(g, "both", foo = "weight")) +}) + +test_that("as_adjacency_matrix() dense/sparse parity for arbitrary weights", { + g <- make_ring(6, directed = TRUE) + w <- c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5) + for (type in c("both", "upper", "lower")) { + A_dense <- as_adjacency_matrix(g, weights = w, type = type, sparse = FALSE) + A_sparse <- as_adjacency_matrix(g, weights = w, type = type, sparse = TRUE) + expect_equal(A_dense, as.matrix(A_sparse), info = type) + } +}) + +# --- as_biadjacency_matrix() weights modes --------------------------------- + +test_that("as_biadjacency_matrix() picks up `weight` attribute by default", { + g <- make_bipartite_graph(c(0, 1, 0, 1, 0, 0), c(1, 2, 2, 3, 3, 4)) + E(g)$weight <- c(2, 4, 6) + expected <- as_biadjacency_matrix(g, weights = "weight", sparse = FALSE) + expect_equal(as_biadjacency_matrix(g, sparse = FALSE), expected) + expect_equal( + as.matrix(as_biadjacency_matrix(g, sparse = TRUE)), + as.matrix(expected) + ) +}) + +test_that("as_biadjacency_matrix() weights = NA returns unweighted matrix", { + g <- make_bipartite_graph(c(0, 1, 0, 1, 0, 0), c(1, 2, 2, 3, 3, 4)) + E(g)$weight <- c(2, 4, 6) + unwt <- as_biadjacency_matrix(g, weights = NA, sparse = FALSE) + # plain biadjacency contains only 0 and 1 + expect_true(all(unwt %in% c(0, 1))) +}) + +test_that("as_biadjacency_matrix() accepts a numeric weights vector", { + g <- make_bipartite_graph(c(0, 1, 0, 1, 0, 0), c(1, 2, 2, 3, 3, 4)) + w <- c(9, 99, 999) + A_dense <- as_biadjacency_matrix(g, weights = w, sparse = FALSE) + A_sparse <- as_biadjacency_matrix(g, weights = w, sparse = TRUE) + expect_equal(A_dense, as.matrix(A_sparse)) + expect_setequal(setdiff(unique(as.vector(A_dense)), 0), w) +}) + +test_that("as_biadjacency_matrix(attr =) is deprecated but still works", { + g <- make_bipartite_graph(c(0, 1, 0, 1, 0, 0), c(1, 2, 2, 3, 3, 4)) + E(g)$weight <- c(2, 4, 6) + expected <- as_biadjacency_matrix(g, weights = "weight", sparse = FALSE) + expect_snapshot( + A <- as_biadjacency_matrix(g, attr = "weight", sparse = FALSE) + ) + expect_equal(A, expected) +}) + +test_that("as_biadjacency_matrix() recovers legacy positional `attr`", { + g <- make_bipartite_graph(c(0, 1, 0, 1, 0, 0), c(1, 2, 2, 3, 3, 4)) + E(g)$weight <- c(2, 4, 6) + expected <- as_biadjacency_matrix(g, weights = "weight", sparse = FALSE) + expect_snapshot( + A <- as_biadjacency_matrix(g, NULL, "weight", sparse = FALSE) + ) + expect_equal(A, expected) +}) + +test_that("as_biadjacency_matrix() keeps hard errors for unrecoverable dots", { + g <- make_bipartite_graph(c(0, 1, 0, 1, 0, 0), c(1, 2, 2, 3, 3, 4)) + expect_error(as_biadjacency_matrix(g, NULL, 1:3)) + expect_error(as_biadjacency_matrix(g, NULL, "weight", "extra")) + expect_error(as_biadjacency_matrix(g, NULL, foo = "weight")) +}) + test_that("as_adj works", { g <- sample_gnp(50, 1 / 50) A <- as_adjacency_matrix(g, sparse = FALSE) @@ -543,8 +712,8 @@ test_that("graphNEL conversion works", { expect_isomorphic(g, g2) expect_equal(V(g)$name, V(g2)$name) - A <- as_adjacency_matrix(g, attr = "weight", sparse = FALSE) - A2 <- as_adjacency_matrix(g2, attr = "weight", sparse = FALSE) + A <- as_adjacency_matrix(g, weights = "weight", sparse = FALSE) + A2 <- as_adjacency_matrix(g2, weights = "weight", sparse = FALSE) expect_equal(A, A) expect_equal(g$name, g2$name) }) @@ -735,16 +904,20 @@ test_that("as_adjacency_matrix() comprehensive snapshot tests", { # Directed, weighted, sparse g_dir_wt <- g_dir_unwt E(g_dir_wt)$weight <- c(1.5, 2.3, 3.7, 0.5) - expect_snapshot(as_adjacency_matrix(g_dir_wt, attr = "weight", sparse = TRUE)) expect_snapshot(as_adjacency_matrix( g_dir_wt, - attr = "weight", + weights = "weight", + sparse = TRUE + )) + expect_snapshot(as_adjacency_matrix( + g_dir_wt, + weights = "weight", type = "upper", sparse = TRUE )) expect_snapshot(as_adjacency_matrix( g_dir_wt, - attr = "weight", + weights = "weight", type = "lower", sparse = TRUE )) @@ -752,18 +925,18 @@ test_that("as_adjacency_matrix() comprehensive snapshot tests", { # Directed, weighted, dense expect_snapshot(as_adjacency_matrix( g_dir_wt, - attr = "weight", + weights = "weight", sparse = FALSE )) expect_snapshot(as_adjacency_matrix( g_dir_wt, - attr = "weight", + weights = "weight", type = "upper", sparse = FALSE )) expect_snapshot(as_adjacency_matrix( g_dir_wt, - attr = "weight", + weights = "weight", type = "lower", sparse = FALSE )) @@ -813,24 +986,24 @@ test_that("as_adjacency_matrix() comprehensive snapshot tests", { E(g_undir_wt)$weight <- c(2.1, 3.2, 4.3) expect_snapshot(as_adjacency_matrix( g_undir_wt, - attr = "weight", + weights = "weight", sparse = TRUE )) expect_snapshot(as_adjacency_matrix( g_undir_wt, - attr = "weight", + weights = "weight", type = "upper", sparse = TRUE )) expect_snapshot(as_adjacency_matrix( g_undir_wt, - attr = "weight", + weights = "weight", type = "lower", sparse = TRUE )) expect_snapshot(as_adjacency_matrix( g_undir_wt, - attr = "weight", + weights = "weight", type = "both", sparse = TRUE )) @@ -838,24 +1011,24 @@ test_that("as_adjacency_matrix() comprehensive snapshot tests", { # Undirected, weighted, dense expect_snapshot(as_adjacency_matrix( g_undir_wt, - attr = "weight", + weights = "weight", sparse = FALSE )) expect_snapshot(as_adjacency_matrix( g_undir_wt, - attr = "weight", + weights = "weight", type = "upper", sparse = FALSE )) expect_snapshot(as_adjacency_matrix( g_undir_wt, - attr = "weight", + weights = "weight", type = "lower", sparse = FALSE )) expect_snapshot(as_adjacency_matrix( g_undir_wt, - attr = "weight", + weights = "weight", type = "both", sparse = FALSE )) diff --git a/tests/testthat/test-incidence.R b/tests/testthat/test-incidence.R index 68b5fb34d03..359aa1c6952 100644 --- a/tests/testthat/test-incidence.R +++ b/tests/testthat/test-incidence.R @@ -106,7 +106,7 @@ test_that("graph_from_biadjacency_matrix() works - dense, modes, weighted", { mode = "out", weighted = TRUE ) - expect_equal(inc_frac, as_biadjacency_matrix(frac_g, attr = "weight")) + expect_equal(inc_frac, as_biadjacency_matrix(frac_g, weights = "weight")) }) test_that("graph_from_biadjacency_matrix() works -- sparse", { diff --git a/tests/testthat/test-structural-properties.R b/tests/testthat/test-structural-properties.R index 0e93e26f3ed..2d61dd82df6 100644 --- a/tests/testthat/test-structural-properties.R +++ b/tests/testthat/test-structural-properties.R @@ -542,10 +542,10 @@ test_that("local transitivity() produces named vectors", { }) test_that("constraint() works", { - constraint.orig <- function(graph, nodes = V(graph), attr = NULL) { + constraint.orig <- function(graph, nodes = V(graph), weights = NA) { ensure_igraph(graph) idx <- degree(graph) != 0 - A <- as_adjacency_matrix(graph, attr = attr, sparse = FALSE) + A <- as_adjacency_matrix(graph, weights = weights, sparse = FALSE) A <- A[idx, idx] n <- sum(idx) @@ -576,7 +576,7 @@ test_that("constraint() works", { withr::local_seed(42) E(karate)$weight <- sample(1:10, replace = TRUE, ecount(karate)) wc1 <- constraint(karate) - wc2 <- constraint.orig(karate, attr = "weight") + wc2 <- constraint.orig(karate, weights = "weight") expect_equal(wc1, wc2) })