From 70aaeed4e5adeb6189d66e1e152d35b163d60c27 Mon Sep 17 00:00:00 2001 From: David Schoch Date: Thu, 28 May 2026 19:33:19 +0200 Subject: [PATCH 1/9] feat: retire `attr` in favor of `weights` for adjacency matrix functions Introduces a `weights` argument on `as_adjacency_matrix()` / `as_adj()` and `as_biadjacency_matrix()` following the standard rigraph weight convention (`NULL` auto-picks `weight`, `NA` forces unweighted, numeric vector is used directly, character scalar names an attribute). The legacy `attr` argument is now soft-deprecated via `lifecycle::deprecate_warn()`. Centrality functions that previously routed weights through a temporary `weight` edge attribute have been simplified to use the new vector path directly: `alpha_centrality()` drops the graph-mutation workaround (#910), and `power_centrality()` gains a `weights` argument (#903). This also structurally fixes the bug class behind #915. Closes: #906, #903, #910, partially #1137 --- R/centrality.R | 68 ++---- R/conversion.R | 232 +++++++++++++------- R/indexing.R | 6 +- man/as_adj.Rd | 29 ++- man/as_adjacency_matrix.Rd | 32 +-- man/as_biadjacency_matrix.Rd | 26 ++- man/power_centrality.Rd | 9 +- tests/testthat/_snaps/conversion.md | 80 +++++-- tests/testthat/test-centrality.R | 63 ++++++ tests/testthat/test-conversion.R | 178 ++++++++++++--- tests/testthat/test-incidence.R | 2 +- tests/testthat/test-structural-properties.R | 6 +- 12 files changed, 518 insertions(+), 213 deletions(-) diff --git a/R/centrality.R b/R/centrality.R index ea707ea6dd7..bbab233f0f2 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,11 @@ 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 +#' @param weights Edge weights to use, following the standard rigraph weight +#' convention. `NULL` (default) uses the graph's `weight` edge attribute if +#' present; `NA` forces an unweighted calculation; a numeric vector of length +#' [ecount()] is used directly; a character scalar names an edge attribute to +#' use. #' @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 +1943,18 @@ 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 +1978,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 +2007,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( diff --git a/R/conversion.R b/R/conversion.R index 67f83ded85e..939564bb1b9 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -219,10 +219,81 @@ 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, call = rlang::caller_env()) { + if (is.null(weights)) { + if ("weight" %in% edge_attr_names(graph)) { + value <- edge_attr(graph, "weight") + if (!is.numeric(value) && !is.logical(value)) { + cli::cli_abort( + c( + "Matrices must be either numeric or logical, and the edge attribute is not", + i = "Pass {.code weights = NA} to ignore the {.val weight} attribute." + ), + call = call + ) + } + return(as.numeric(value)) + } + 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) + } + value <- edge_attr(graph, weights) + 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 + ) + } + return(as.numeric(value)) + } + + 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) +} + get.adjacency.dense <- function( graph, type = c("both", "upper", "lower"), - attr = NULL, + weights = numeric(), loops = c("once", "twice", "ignore"), names = TRUE ) { @@ -247,8 +318,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 +330,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 +344,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 tmp <- get_adjacency_sparse_impl( graph, @@ -334,18 +385,21 @@ 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 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,13 +419,15 @@ 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") @@ -382,13 +438,24 @@ as_adjacency_matrix <- function( lifecycle::deprecate_stop("2.0.0", "as_adjacency_matrix(edges = )") } + if (lifecycle::is_present(attr)) { + lifecycle::deprecate_warn( + "3.0.0", + "as_adjacency_matrix(attr = )", + "as_adjacency_matrix(weights = )" + ) + weights <- attr + } + + weights <- resolve_edge_weights(graph, weights) + 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 +474,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 +485,7 @@ as_adj <- function( as_adjacency_matrix( graph = graph, type = type, + weights = weights, attr = attr, edges = edges, names = names, @@ -916,10 +985,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 +1006,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 +1027,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 +1044,7 @@ get.incidence.sparse <- function( graph, types, names, - attr, + weights = numeric(), call = rlang::caller_env() ) { types <- handle_vertex_type_arg(types, graph) @@ -1017,21 +1073,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 +1103,22 @@ 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. +#' @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) bipartite 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 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 +1143,38 @@ 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) + if (lifecycle::is_present(attr)) { + lifecycle::deprecate_warn( + "3.0.0", + "as_biadjacency_matrix(attr = )", + "as_biadjacency_matrix(weights = )" + ) + weights <- attr + } + names <- as.logical(names) sparse <- as.logical(sparse) + weights <- resolve_edge_weights(graph, weights) + 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/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..1e84cf7aa5f 100644 --- a/man/as_adjacency_matrix.Rd +++ b/man/as_adjacency_matrix.Rd @@ -7,7 +7,8 @@ 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 +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.} @@ -71,7 +76,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..5a1a16df4a6 100644 --- a/man/as_biadjacency_matrix.Rd +++ b/man/as_biadjacency_matrix.Rd @@ -7,7 +7,8 @@ as_biadjacency_matrix( graph, types = NULL, - attr = NULL, + weights = NULL, + attr = deprecated(), names = TRUE, sparse = FALSE ) @@ -20,12 +21,23 @@ 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{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) bipartite 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/power_centrality.Rd b/man/power_centrality.Rd index bce0108e8ab..d6445e5d4cc 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,12 @@ 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}{Edge weights to use, following the standard rigraph weight +convention. \code{NULL} (default) uses the graph's \code{weight} edge attribute if +present; \code{NA} forces an unweighted calculation; a numeric vector of length +\code{\link[=ecount]{ecount()}} is used directly; a character scalar names an edge attribute to +use.} } \value{ A vector, containing the centrality scores. diff --git a/tests/testthat/_snaps/conversion.md b/tests/testthat/_snaps/conversion.md index a7afcea5935..3cfbb923c83 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,7 +31,7 @@ --- 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 @@ -39,7 +39,7 @@ # 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,11 +47,55 @@ --- 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 +# 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_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_long_data_frame() works correctly with and without names Code @@ -161,7 +205,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 +216,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 +227,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 +238,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 +248,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 +258,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 +352,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 +363,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 +374,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 +385,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 +396,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 +406,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 +416,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 +426,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..4a62d1be407 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,7 @@ 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 +198,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 +222,7 @@ 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 +243,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 +260,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 +279,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 +296,130 @@ 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() 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_adj works", { g <- sample_gnp(50, 1 / 50) A <- as_adjacency_matrix(g, sparse = FALSE) @@ -543,8 +667,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 +859,16 @@ 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, weights = "weight", sparse = TRUE)) expect_snapshot(as_adjacency_matrix( g_dir_wt, - attr = "weight", + weights = "weight", type = "upper", sparse = TRUE )) expect_snapshot(as_adjacency_matrix( g_dir_wt, - attr = "weight", + weights = "weight", type = "lower", sparse = TRUE )) @@ -752,18 +876,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 +937,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 +962,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) }) From c46a556a0abf6c5c8e3a63f73af22793d86c8bec Mon Sep 17 00:00:00 2001 From: schochastics Date: Thu, 28 May 2026 17:42:05 +0000 Subject: [PATCH 2/9] chore: Auto-update from GitHub Actions Run: https://github.com/igraph/rigraph/actions/runs/26591333825 --- R/centrality.R | 16 ++++++++++++++-- tests/testthat/test-conversion.R | 18 +++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/R/centrality.R b/R/centrality.R index bbab233f0f2..c88eea41632 100644 --- a/R/centrality.R +++ b/R/centrality.R @@ -1949,11 +1949,23 @@ power_centrality <- function( nodes <- as_igraph_vs(graph, nodes) if (sparse) { res <- bonpow.sparse( - graph, nodes, loops, exponent, rescale, tol, weights = weights + graph, + nodes, + loops, + exponent, + rescale, + tol, + weights = weights ) } else { res <- bonpow.dense( - graph, nodes, loops, exponent, rescale, tol, weights = weights + graph, + nodes, + loops, + exponent, + rescale, + tol, + weights = weights ) } diff --git a/tests/testthat/test-conversion.R b/tests/testthat/test-conversion.R index 4a62d1be407..355d4fb5707 100644 --- a/tests/testthat/test-conversion.R +++ b/tests/testthat/test-conversion.R @@ -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, weights = "weight", sparse = FALSE) + weight_adj_matrix <- as_adjacency_matrix( + g, + weights = "weight", + sparse = FALSE + ) expect_equal( weight_adj_matrix, matrix( @@ -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, weights = "weight") + weight_adj_matrix <- as_adjacency_matrix( + ug, + sparse = FALSE, + weights = "weight" + ) dimnames(weight_adj_matrix) <- NULL expect_equal( weight_adj_matrix, @@ -859,7 +867,11 @@ 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, weights = "weight", sparse = TRUE)) + expect_snapshot(as_adjacency_matrix( + g_dir_wt, + weights = "weight", + sparse = TRUE + )) expect_snapshot(as_adjacency_matrix( g_dir_wt, weights = "weight", From e3ee542b4ed9d79cfb2b1dc19a7b54c09e74ea02 Mon Sep 17 00:00:00 2001 From: David Schoch Date: Thu, 28 May 2026 20:17:10 +0200 Subject: [PATCH 3/9] chore: revert manual Rd edits (CI regenerates them) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit hand-edited four Rd files for the functions whose signatures changed. The package's CI regenerates `man/*.Rd` from the roxygen comments in `R/`, so committing those edits adds nothing — the source-of-truth roxygen blocks are already updated in the same commit. Removing them keeps the diff minimal and avoids divergence from the canonical generator. Co-Authored-By: Claude Opus 4.7 (1M context) --- man/as_adj.Rd | 29 ++++++++++++----------------- man/as_adjacency_matrix.Rd | 32 +++++++++++++------------------- man/as_biadjacency_matrix.Rd | 26 +++++++------------------- man/power_centrality.Rd | 9 +-------- 4 files changed, 33 insertions(+), 63 deletions(-) diff --git a/man/as_adj.Rd b/man/as_adj.Rd index c1e6fb033de..61f797bb585 100644 --- a/man/as_adj.Rd +++ b/man/as_adj.Rd @@ -7,8 +7,7 @@ as_adj( graph, type = c("both", "upper", "lower"), - weights = NULL, - attr = deprecated(), + attr = NULL, edges = deprecated(), names = TRUE, sparse = igraph_opt("sparsematrices") @@ -23,22 +22,18 @@ 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{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}{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{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.} +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{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 1e84cf7aa5f..ee5b1ca1cd0 100644 --- a/man/as_adjacency_matrix.Rd +++ b/man/as_adjacency_matrix.Rd @@ -7,8 +7,7 @@ as_adjacency_matrix( graph, type = c("both", "upper", "lower"), - weights = NULL, - attr = deprecated(), + attr = NULL, edges = deprecated(), names = TRUE, sparse = igraph_opt("sparsematrices") @@ -23,22 +22,18 @@ 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{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}{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{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.} +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{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.} @@ -76,8 +71,7 @@ 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) -as_adjacency_matrix(g, weights = NA) +as_adjacency_matrix(g, attr = "weight") } \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 5a1a16df4a6..ee9b545edb1 100644 --- a/man/as_biadjacency_matrix.Rd +++ b/man/as_biadjacency_matrix.Rd @@ -7,8 +7,7 @@ as_biadjacency_matrix( graph, types = NULL, - weights = NULL, - attr = deprecated(), + attr = NULL, names = TRUE, sparse = FALSE ) @@ -21,23 +20,12 @@ directed graphs.} \code{type} vertex attribute. You must supply this argument if the graph has no \code{type} vertex attribute.} -\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) bipartite 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{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{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 d6445e5d4cc..bce0108e8ab 100644 --- a/man/power_centrality.Rd +++ b/man/power_centrality.Rd @@ -11,8 +11,7 @@ power_centrality( exponent = 1, rescale = FALSE, tol = 1e-07, - sparse = TRUE, - weights = NULL + sparse = TRUE ) } \arguments{ @@ -37,12 +36,6 @@ 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}{Edge weights to use, following the standard rigraph weight -convention. \code{NULL} (default) uses the graph's \code{weight} edge attribute if -present; \code{NA} forces an unweighted calculation; a numeric vector of length -\code{\link[=ecount]{ecount()}} is used directly; a character scalar names an edge attribute to -use.} } \value{ A vector, containing the centrality scores. From f1b705d1237a7badf91569a45944c55acc810e8c Mon Sep 17 00:00:00 2001 From: David Schoch Date: Thu, 28 May 2026 21:13:28 +0200 Subject: [PATCH 4/9] document --- man/as_adj.Rd | 29 +++++++++++++++++------------ man/as_adjacency_matrix.Rd | 32 +++++++++++++++++++------------- man/as_biadjacency_matrix.Rd | 26 +++++++++++++++++++------- man/get.adjacency.Rd | 15 +++------------ man/get.incidence.Rd | 9 +++------ man/power_centrality.Rd | 19 +++++++++++++------ 6 files changed, 74 insertions(+), 56 deletions(-) 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..1e84cf7aa5f 100644 --- a/man/as_adjacency_matrix.Rd +++ b/man/as_adjacency_matrix.Rd @@ -7,7 +7,8 @@ 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 +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.} @@ -71,7 +76,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..5a1a16df4a6 100644 --- a/man/as_biadjacency_matrix.Rd +++ b/man/as_biadjacency_matrix.Rd @@ -7,7 +7,8 @@ as_biadjacency_matrix( graph, types = NULL, - attr = NULL, + weights = NULL, + attr = deprecated(), names = TRUE, sparse = FALSE ) @@ -20,12 +21,23 @@ 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{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) bipartite 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/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..9cb3c8bd541 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,12 @@ 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}{Edge weights to use, following the standard rigraph weight +convention. \code{NULL} (default) uses the graph's \code{weight} edge attribute if +present; \code{NA} forces an unweighted calculation; a numeric vector of length +\code{\link[=ecount]{ecount()}} is used directly; a character scalar names an edge attribute to +use.} } \value{ A vector, containing the centrality scores. @@ -103,7 +110,7 @@ fixed when we get a better algorithm. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Operators.html#igraph_simplify}{\code{simplify()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_degree}{\code{degree()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_adjacency}{\code{get_adjacency()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_adjacency_sparse}{\code{get_adjacency_sparse()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Operators.html#igraph_simplify}{\code{simplify()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_adjacency}{\code{get_adjacency()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_adjacency_sparse}{\code{get_adjacency_sparse()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \examples{ @@ -117,19 +124,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)) } From 97bbcd62dea280fe7e4a2af93b39a949baf3fdba Mon Sep 17 00:00:00 2001 From: David Schoch Date: Thu, 28 May 2026 21:59:19 +0200 Subject: [PATCH 5/9] Refactor resolve_edge_weights to handle deprecated `attr` parameter and extract edge_attr_as_weights helper; add `...` for future extensions to as_adjacency_matrix and as_biadjacency_matrix --- R/conversion.R | 90 ++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/R/conversion.R b/R/conversion.R index 939564bb1b9..faa4034dac9 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -230,20 +230,25 @@ get.adjedgelist <- function( # # Returns: numeric() (empty) for unweighted, or a numeric vector of length # ecount(graph) otherwise. -resolve_edge_weights <- function(graph, weights, call = rlang::caller_env()) { +resolve_edge_weights <- function( + graph, + weights, + attr = deprecated(), + fn = "function", + call = rlang::caller_env() +) { + if (lifecycle::is_present(attr)) { + lifecycle::deprecate_soft( + "3.0.0", + sprintf("%s(attr = )", fn), + sprintf("%s(weights = )", fn) + ) + weights <- attr + } + if (is.null(weights)) { if ("weight" %in% edge_attr_names(graph)) { - value <- edge_attr(graph, "weight") - if (!is.numeric(value) && !is.logical(value)) { - cli::cli_abort( - c( - "Matrices must be either numeric or logical, and the edge attribute is not", - i = "Pass {.code weights = NA} to ignore the {.val weight} attribute." - ), - call = call - ) - } - return(as.numeric(value)) + return(edge_attr_as_weights(graph, "weight", call)) } return(numeric()) } @@ -262,14 +267,7 @@ resolve_edge_weights <- function(graph, weights, call = rlang::caller_env()) { if (!weights %in% edge_attr_names(graph)) { cli::cli_abort("No such edge attribute", call = call) } - value <- edge_attr(graph, weights) - 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 - ) - } - return(as.numeric(value)) + return(edge_attr_as_weights(graph, weights, call)) } if (!is.numeric(weights) && !is.logical(weights)) { @@ -290,6 +288,20 @@ resolve_edge_weights <- function(graph, weights, call = rlang::caller_env()) { 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) +} + get.adjacency.dense <- function( graph, type = c("both", "upper", "lower"), @@ -385,6 +397,7 @@ 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 ... 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 @@ -426,6 +439,7 @@ get.adjacency.sparse <- function( as_adjacency_matrix <- function( graph, type = c("both", "upper", "lower"), + ..., weights = NULL, attr = deprecated(), edges = deprecated(), @@ -433,21 +447,18 @@ as_adjacency_matrix <- function( sparse = igraph_opt("sparsematrices") ) { ensure_igraph(graph) + rlang::check_dots_empty() if (lifecycle::is_present(edges) && isTRUE(edges)) { lifecycle::deprecate_stop("2.0.0", "as_adjacency_matrix(edges = )") } - if (lifecycle::is_present(attr)) { - lifecycle::deprecate_warn( - "3.0.0", - "as_adjacency_matrix(attr = )", - "as_adjacency_matrix(weights = )" - ) - weights <- attr - } - - weights <- resolve_edge_weights(graph, weights) + weights <- resolve_edge_weights( + graph, + weights, + attr, + fn = "as_adjacency_matrix" + ) if (sparse) { get.adjacency.sparse(graph, type = type, weights = weights, names = names) @@ -1103,6 +1114,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 ... 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 @@ -1143,6 +1155,7 @@ get.incidence.sparse <- function( as_biadjacency_matrix <- function( graph, types = NULL, + ..., weights = NULL, attr = deprecated(), names = TRUE, @@ -1150,20 +1163,17 @@ as_biadjacency_matrix <- function( ) { # Argument checks ensure_igraph(graph) - - if (lifecycle::is_present(attr)) { - lifecycle::deprecate_warn( - "3.0.0", - "as_biadjacency_matrix(attr = )", - "as_biadjacency_matrix(weights = )" - ) - weights <- attr - } + rlang::check_dots_empty() names <- as.logical(names) sparse <- as.logical(sparse) - weights <- resolve_edge_weights(graph, weights) + weights <- resolve_edge_weights( + graph, + weights, + attr, + fn = "as_biadjacency_matrix" + ) if (sparse) { get.incidence.sparse( From 47ef17f487d9652186ffd70e700d154901459bab Mon Sep 17 00:00:00 2001 From: David Schoch Date: Thu, 28 May 2026 22:00:42 +0200 Subject: [PATCH 6/9] doc --- man/as_adjacency_matrix.Rd | 3 +++ man/as_biadjacency_matrix.Rd | 3 +++ man/as_incidence_matrix.Rd | 3 +++ 3 files changed, 9 insertions(+) diff --git a/man/as_adjacency_matrix.Rd b/man/as_adjacency_matrix.Rd index 1e84cf7aa5f..8e0a6712571 100644 --- a/man/as_adjacency_matrix.Rd +++ b/man/as_adjacency_matrix.Rd @@ -7,6 +7,7 @@ as_adjacency_matrix( graph, type = c("both", "upper", "lower"), + ..., weights = NULL, attr = deprecated(), edges = deprecated(), @@ -23,6 +24,8 @@ 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{...}{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 diff --git a/man/as_biadjacency_matrix.Rd b/man/as_biadjacency_matrix.Rd index 5a1a16df4a6..54da9416e08 100644 --- a/man/as_biadjacency_matrix.Rd +++ b/man/as_biadjacency_matrix.Rd @@ -7,6 +7,7 @@ as_biadjacency_matrix( graph, types = NULL, + ..., weights = NULL, attr = deprecated(), names = TRUE, @@ -21,6 +22,8 @@ directed graphs.} \code{type} vertex attribute. You must supply this argument if the graph has no \code{type} vertex attribute.} +\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 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]}} From 0df5d1fffdda0447740eefabb6115496c0b642db Mon Sep 17 00:00:00 2001 From: schochastics Date: Thu, 28 May 2026 20:08:49 +0000 Subject: [PATCH 7/9] chore: Auto-update from GitHub Actions Run: https://github.com/igraph/rigraph/actions/runs/26598981879 --- tests/testthat/_snaps/conversion.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/testthat/_snaps/conversion.md b/tests/testthat/_snaps/conversion.md index 3cfbb923c83..dd796ca286b 100644 --- a/tests/testthat/_snaps/conversion.md +++ b/tests/testthat/_snaps/conversion.md @@ -34,7 +34,8 @@ 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 @@ -50,7 +51,8 @@ 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 From 9d6f0d381361892241225469d40474e67206368d Mon Sep 17 00:00:00 2001 From: David Schoch Date: Fri, 29 May 2026 09:13:46 +0200 Subject: [PATCH 8/9] Unify weights parameter docs via @inheritParams as_adjacency_matrix --- R/centrality.R | 12 ++---------- R/conversion.R | 18 +----------------- man/alpha.centrality.Rd | 17 ++++++++++++----- man/alpha_centrality.Rd | 17 ++++++++++++----- man/as_biadjacency_matrix.Rd | 3 +-- man/power_centrality.Rd | 19 +++++++++++++------ 6 files changed, 41 insertions(+), 45 deletions(-) diff --git a/R/centrality.R b/R/centrality.R index c88eea41632..577a5765f7c 100644 --- a/R/centrality.R +++ b/R/centrality.R @@ -1886,11 +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 -#' @param weights Edge weights to use, following the standard rigraph weight -#' convention. `NULL` (default) uses the graph's `weight` edge attribute if -#' present; `NA` forces an unweighted calculation; a numeric vector of length -#' [ecount()] is used directly; a character scalar names an edge attribute to -#' use. +#' @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 @@ -2070,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 faa4034dac9..451b56922ab 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -1114,23 +1114,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 ... 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) bipartite 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. +#' @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 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_biadjacency_matrix.Rd b/man/as_biadjacency_matrix.Rd index 54da9416e08..755e6a54587 100644 --- a/man/as_biadjacency_matrix.Rd +++ b/man/as_biadjacency_matrix.Rd @@ -27,8 +27,7 @@ no \code{type} vertex attribute.} \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) bipartite adjacency -matrix. +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. diff --git a/man/power_centrality.Rd b/man/power_centrality.Rd index 9cb3c8bd541..1a82a203d75 100644 --- a/man/power_centrality.Rd +++ b/man/power_centrality.Rd @@ -38,11 +38,18 @@ score; can be negative} calculation. The \sQuote{Matrix} package is required for sparse matrix support} -\item{weights}{Edge weights to use, following the standard rigraph weight -convention. \code{NULL} (default) uses the graph's \code{weight} edge attribute if -present; \code{NA} forces an unweighted calculation; a numeric vector of length -\code{\link[=ecount]{ecount()}} is used directly; a character scalar names an edge attribute to -use.} +\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. @@ -110,7 +117,7 @@ fixed when we get a better algorithm. } \section{Related documentation in the C library}{ -\href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Operators.html#igraph_simplify}{\code{simplify()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_adjacency}{\code{get_adjacency()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_adjacency_sparse}{\code{get_adjacency_sparse()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} +\href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_vcount}{\code{vcount()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Operators.html#igraph_simplify}{\code{simplify()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_degree}{\code{degree()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_adjacency}{\code{get_adjacency()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Structural.html#igraph_get_adjacency_sparse}{\code{get_adjacency_sparse()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_edges}{\code{edges()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_get_eids}{\code{get_eids()}}, \href{https://igraph.org/c/html/0.10.17/igraph-Basic.html#igraph_ecount}{\code{ecount()}} } \examples{ From d783eec8a9db1f2c558e9081cfbb5b8b4d49eb06 Mon Sep 17 00:00:00 2001 From: David Schoch Date: Fri, 29 May 2026 21:25:32 +0200 Subject: [PATCH 9/9] Recover legacy positional `attr` argument in as_adjacency_matrix() and as_biadjacency_matrix() via `...` with soft deprecation --- R/conversion.R | 94 +++++++++++++++++++++++++++-- tests/testthat/_snaps/conversion.md | 20 ++++++ tests/testthat/test-conversion.R | 37 ++++++++++++ 3 files changed, 145 insertions(+), 6 deletions(-) diff --git a/R/conversion.R b/R/conversion.R index 451b56922ab..1ff67077e48 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -235,13 +235,15 @@ resolve_edge_weights <- function( weights, attr = deprecated(), fn = "function", - call = rlang::caller_env() + 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) + sprintf("%s(weights = )", fn), + user_env = user_env ) weights <- attr } @@ -302,6 +304,56 @@ edge_attr_as_weights <- function(graph, name, 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"), @@ -447,7 +499,22 @@ as_adjacency_matrix <- function( sparse = igraph_opt("sparsematrices") ) { ensure_igraph(graph) - rlang::check_dots_empty() + + 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 = )") @@ -457,7 +524,8 @@ as_adjacency_matrix <- function( graph, weights, attr, - fn = "as_adjacency_matrix" + fn = "as_adjacency_matrix", + user_env = user_env ) if (sparse) { @@ -1147,7 +1215,20 @@ as_biadjacency_matrix <- function( ) { # Argument checks ensure_igraph(graph) - rlang::check_dots_empty() + + 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) @@ -1156,7 +1237,8 @@ as_biadjacency_matrix <- function( graph, weights, attr, - fn = "as_biadjacency_matrix" + fn = "as_biadjacency_matrix", + user_env = user_env ) if (sparse) { diff --git a/tests/testthat/_snaps/conversion.md b/tests/testthat/_snaps/conversion.md index dd796ca286b..9a002c6334e 100644 --- a/tests/testthat/_snaps/conversion.md +++ b/tests/testthat/_snaps/conversion.md @@ -89,6 +89,16 @@ 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 @@ -98,6 +108,16 @@ 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 Code diff --git a/tests/testthat/test-conversion.R b/tests/testthat/test-conversion.R index 355d4fb5707..fd1e1acb30f 100644 --- a/tests/testthat/test-conversion.R +++ b/tests/testthat/test-conversion.R @@ -378,6 +378,26 @@ test_that("as_adjacency_matrix(attr =) is deprecated but still works", { 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) @@ -428,6 +448,23 @@ test_that("as_biadjacency_matrix(attr =) is deprecated but still works", { 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)