From 4b126672c645e084cd7f015b9326ab83cec89cc5 Mon Sep 17 00:00:00 2001 From: David Skalinder Date: Fri, 20 Oct 2023 15:49:10 -0500 Subject: [PATCH 1/4] Add tests for power centrality weights --- tests/testthat/test-bonpow.R | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/testthat/test-bonpow.R b/tests/testthat/test-bonpow.R index 4da40403c1c..e0f5a7f259d 100644 --- a/tests/testthat/test-bonpow.R +++ b/tests/testthat/test-bonpow.R @@ -66,4 +66,46 @@ test_that("Power centrality works", { c(1.80, 1.48, 0.60) )) ) + + # Remove block after debug + g.c <- make_graph(c(1, 2, 1, 3, 2, 4, 3, 5), dir = FALSE) + g.c.w <- g.c + E(g.c.w)$weight <- seq_len(ecount(g.c.w)) * rep_len(c(1, -1), ecount(g.c.w)) + g.c.w.mat <- as.matrix(as_adj(g.c.w, attr = "weight")) + bp.c.w.sna <- lapply(seq(-.4, .4, by = 0.1), function(x) { + round(sna::bonpow(g.c.w.mat, exponent = x), 2)[c(1, 2, 5)] + }) + expect_that(bp.c.w.sna, equals(list( + c(0.75, 0.47, 1.42), c(0.87, -0.69, 1.13), + c(-0.72, 0.33, -1.38), c(-0.54, 0.61, -1.29), + c(-0.25, 1.01, -1.01), c(0.15, 1.33, -0.45), + c(0.69, 1.01, 0.81), c(0.20, 1.52, -0.58), + c(-0.50, -1.55, 0.00) + ))) + + ## Test weights with sparse and dense fns vs. results from `sna::bonpow()` + g.c.w <- g.c + E(g.c.w)$weight <- seq_len(ecount(g.c.w)) * rep_len(c(1, -1), ecount(g.c.w)) + + bp.c.w.sparse <- lapply(seq(-.4, .4, by = 0.1), function(x) { + round(power_centrality(g.c.w, exponent = x), 2)[c(1, 2, 5)] + }) + expect_that(bp.c.w.sparse, equals(list( + c(0.75, 0.47, 1.42), c(0.87, -0.69, 1.13), + c(-0.72, 0.33, -1.38), c(-0.54, 0.61, -1.29), + c(-0.25, 1.01, -1.01), c(0.15, 1.33, -0.45), + c(0.69, 1.01, 0.81), c(0.20, 1.52, -0.58), + c(-0.50, -1.55, 0.00) + ))) + + bp.c.w.dense <- lapply(seq(-.4, .4, by = 0.1), function(x) { + round(power_centrality(g.c.w, exponent = x, sparse = FALSE), 2)[c(1, 2, 5)] + }) + expect_that(bp.c.w.dense, equals(list( + c(0.75, 0.47, 1.42), c(0.87, -0.69, 1.13), + c(-0.72, 0.33, -1.38), c(-0.54, 0.61, -1.29), + c(-0.25, 1.01, -1.01), c(0.15, 1.33, -0.45), + c(0.69, 1.01, 0.81), c(0.20, 1.52, -0.58), + c(-0.50, -1.55, 0.00) + ))) }) From bdd8726ee63bc2e8a365482ec657e98e1266e0d7 Mon Sep 17 00:00:00 2001 From: David Skalinder Date: Fri, 20 Oct 2023 16:00:30 -0500 Subject: [PATCH 2/4] Enable edge weights for power centrality Logic and docs for the weights argument adapted from eigen_centrality() and eigenvector_centrality_impl(). Partial fix for #903. --- R/centrality.R | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/R/centrality.R b/R/centrality.R index 76e8dd58139..8ff3f70853f 100644 --- a/R/centrality.R +++ b/R/centrality.R @@ -1304,12 +1304,12 @@ harmonic_centrality <- harmonic_centrality_cutoff_impl -bonpow.dense <- function(graph, nodes = V(graph), - loops = FALSE, exponent = 1, +bonpow.dense <- function(graph, nodes = V(graph), loops = FALSE, + exponent = 1, weights = NULL, rescale = FALSE, tol = 1e-7) { ensure_igraph(graph) - d <- as_adj(graph) + d <- as_adj(graph, attr = weights) if (!loops) { diag(d) <- 0 } @@ -1328,7 +1328,8 @@ bonpow.dense <- function(graph, nodes = V(graph), } bonpow.sparse <- function(graph, nodes = V(graph), loops = FALSE, - exponent = 1, rescale = FALSE, tol = 1e-07) { + exponent = 1, weights = NULL, + rescale = FALSE, tol = 1e-07) { ## remove loops if requested if (!loops) { graph <- simplify(graph, remove.multiple = FALSE, remove.loops = TRUE) @@ -1337,13 +1338,13 @@ bonpow.sparse <- function(graph, nodes = V(graph), loops = FALSE, vg <- vcount(graph) ## sparse adjacency matrix - d <- as_adj(graph, sparse = TRUE) + d <- as_adj(graph, attr = 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, strength(graph, mode = "out"), tol = tol) if (rescale) { ev <- ev / sum(ev) @@ -1408,6 +1409,13 @@ bonpow.sparse <- function(graph, nodes = V(graph), loops = FALSE, #' loops. `loops` is `FALSE` by default. #' @param exponent exponent (decay rate) for the Bonacich power centrality #' score; can be negative +#' @param weights A numerical vector or `NULL`. This argument can be used +#' to give edge weights for calculating the weighted eigenvector centrality of +#' vertices. If this is `NULL` and the graph has a `weight` edge +#' attribute then that is used. If `weights` is a numerical vector then it is +#' used, even if the graph has a `weight` edge attribute. If this is +#' `NA`, then no edge weights are used (even if the graph has a +#' `weight` edge attribute). #' @param rescale if true, centrality scores are rescaled such that they sum to #' 1. #' @param tol tolerance for near-singularities during matrix inversion (see @@ -1463,13 +1471,21 @@ bonpow.sparse <- function(graph, nodes = V(graph), loops = FALSE, #' } #' power_centrality <- function(graph, nodes = V(graph), - loops = FALSE, exponent = 1, + loops = FALSE, exponent = 1, weights = NULL, rescale = FALSE, tol = 1e-7, sparse = TRUE) { nodes <- as_igraph_vs(graph, nodes) + + if (is.null(weights) && "weight" %in% edge_attr_names(graph)) { + weights <- "weight" + } + if (any(is.na(weights))) { + weights <- NULL + } + if (sparse) { - res <- bonpow.sparse(graph, nodes, loops, exponent, rescale, tol) + res <- bonpow.sparse(graph, nodes, loops, exponent, weights, rescale, tol) } else { - res <- bonpow.dense(graph, nodes, loops, exponent, rescale, tol) + res <- bonpow.dense(graph, nodes, loops, exponent, weights, rescale, tol) } if (igraph_opt("add.vertex.names") && is_named(graph)) { From 3b699fc6f160277e2aab9eb9d07d29c16a924a65 Mon Sep 17 00:00:00 2001 From: David Skalinder Date: Fri, 20 Oct 2023 16:02:46 -0500 Subject: [PATCH 3/4] Remove test setup --- tests/testthat/test-bonpow.R | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/testthat/test-bonpow.R b/tests/testthat/test-bonpow.R index e0f5a7f259d..9d9b4485062 100644 --- a/tests/testthat/test-bonpow.R +++ b/tests/testthat/test-bonpow.R @@ -67,22 +67,6 @@ test_that("Power centrality works", { )) ) - # Remove block after debug - g.c <- make_graph(c(1, 2, 1, 3, 2, 4, 3, 5), dir = FALSE) - g.c.w <- g.c - E(g.c.w)$weight <- seq_len(ecount(g.c.w)) * rep_len(c(1, -1), ecount(g.c.w)) - g.c.w.mat <- as.matrix(as_adj(g.c.w, attr = "weight")) - bp.c.w.sna <- lapply(seq(-.4, .4, by = 0.1), function(x) { - round(sna::bonpow(g.c.w.mat, exponent = x), 2)[c(1, 2, 5)] - }) - expect_that(bp.c.w.sna, equals(list( - c(0.75, 0.47, 1.42), c(0.87, -0.69, 1.13), - c(-0.72, 0.33, -1.38), c(-0.54, 0.61, -1.29), - c(-0.25, 1.01, -1.01), c(0.15, 1.33, -0.45), - c(0.69, 1.01, 0.81), c(0.20, 1.52, -0.58), - c(-0.50, -1.55, 0.00) - ))) - ## Test weights with sparse and dense fns vs. results from `sna::bonpow()` g.c.w <- g.c E(g.c.w)$weight <- seq_len(ecount(g.c.w)) * rep_len(c(1, -1), ecount(g.c.w)) From 29c51d60b4f7781707209d63a630776dc7060991 Mon Sep 17 00:00:00 2001 From: David Skalinder Date: Fri, 20 Oct 2023 16:14:23 -0500 Subject: [PATCH 4/4] Sync docs --- man/power_centrality.Rd | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/man/power_centrality.Rd b/man/power_centrality.Rd index e046e60d069..83886f15444 100644 --- a/man/power_centrality.Rd +++ b/man/power_centrality.Rd @@ -9,6 +9,7 @@ power_centrality( nodes = V(graph), loops = FALSE, exponent = 1, + weights = NULL, rescale = FALSE, tol = 1e-07, sparse = TRUE @@ -27,6 +28,14 @@ loops. \code{loops} is \code{FALSE} by default.} \item{exponent}{exponent (decay rate) for the Bonacich power centrality score; can be negative} +\item{weights}{A numerical vector or \code{NULL}. This argument can be used +to give edge weights for calculating the weighted eigenvector centrality of +vertices. If this is \code{NULL} and the graph has a \code{weight} edge +attribute then that is used. If \code{weights} is a numerical vector then it is +used, even if the graph has a \code{weight} edge attribute. If this is +\code{NA}, then no edge weights are used (even if the graph has a +\code{weight} edge attribute).} + \item{rescale}{if true, centrality scores are rescaled such that they sum to 1.}