diff --git a/R/assortativity.R b/R/assortativity.R index 8c8e95d0876..448b34bcc44 100644 --- a/R/assortativity.R +++ b/R/assortativity.R @@ -239,13 +239,37 @@ assortativity_legacy <- function( #' with [as.integer()]. Character vectors are converted to integers using #' [as.factor()]. #' @rdname assortativity +#' @inheritParams rlang::args_dots_empty #' @export assortativity_nominal <- function( graph, types, + ..., directed = TRUE, normalized = TRUE ) { + # BEGIN GENERATED ARG_HANDLE: assortativity_nominal, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(directed = directed, normalized = normalized), + recover_new = c("directed", "normalized"), + recover_old = c("directed", "normalized"), + match_names = c("directed", "normalized"), + match_to = c("directed", "normalized"), + defaults = list(directed = TRUE, normalized = TRUE), + head_args = c("graph", "types"), + fn_name = "assortativity_nominal" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + # Convert character types to factor then to integer for categorical data if (is.character(types)) { types <- as.integer(as.factor(types)) @@ -260,8 +284,35 @@ assortativity_nominal <- function( } #' @rdname assortativity +#' @inheritParams rlang::args_dots_empty #' @export -assortativity_degree <- function(graph, directed = TRUE) { +assortativity_degree <- function( + graph, + ..., + directed = TRUE +) { + # BEGIN GENERATED ARG_HANDLE: assortativity_degree, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(directed = directed), + recover_new = c("directed"), + recover_old = c("directed"), + match_names = c("directed"), + match_to = c("directed"), + defaults = list(directed = TRUE), + head_args = c("graph"), + fn_name = "assortativity_degree" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + assortativity_degree_impl( graph = graph, directed = directed diff --git a/R/cycles.R b/R/cycles.R index c3d65b5cae0..25356203038 100644 --- a/R/cycles.R +++ b/R/cycles.R @@ -31,6 +31,7 @@ #' a specific cycle. #' #' @param graph The input graph. +#' @inheritParams rlang::args_dots_empty #' @param mode Character constant specifying how to handle directed graphs. #' `out` follows edge directions, `in` follows edges in the reverse direction, #' and `all` ignores edge directions. Ignored in undirected graphs. diff --git a/R/efficiency.R b/R/efficiency.R index fd628c45d90..51f6f349749 100644 --- a/R/efficiency.R +++ b/R/efficiency.R @@ -37,6 +37,7 @@ #' efficiency above. #' #' @param graph The graph to analyze. +#' @inheritParams rlang::args_dots_empty #' @param weights The edge weights. All edge weights must be non-negative; #' additionally, no edge weight may be NaN. If it is `NULL` (the default) #' and the graph has a `weight` edge attribute, then it is used automatically. @@ -66,7 +67,34 @@ #' g <- make_graph("zachary") #' global_efficiency(g) #' average_local_efficiency(g) -global_efficiency <- function(graph, weights = NULL, directed = TRUE) { +global_efficiency <- function( + graph, + ..., + weights = NULL, + directed = TRUE +) { + # BEGIN GENERATED ARG_HANDLE: global_efficiency, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(weights = weights, directed = directed), + recover_new = c("weights", "directed"), + recover_old = c("weights", "directed"), + match_names = c("weights", "directed"), + match_to = c("weights", "directed"), + defaults = list(weights = NULL, directed = TRUE), + head_args = c("graph"), + fn_name = "global_efficiency" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + global_efficiency_impl( graph = graph, weights = weights, @@ -75,14 +103,42 @@ global_efficiency <- function(graph, weights = NULL, directed = TRUE) { } #' @rdname global_efficiency +#' @inheritParams rlang::args_dots_empty #' @export local_efficiency <- function( graph, vids = V(graph), + ..., weights = NULL, directed = TRUE, mode = c("all", "out", "in", "total") ) { + # BEGIN GENERATED ARG_HANDLE: local_efficiency, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(weights = weights, directed = directed, mode = mode), + recover_new = c("weights", "directed", "mode"), + recover_old = c("weights", "directed", "mode"), + match_names = c("weights", "directed", "mode"), + match_to = c("weights", "directed", "mode"), + defaults = list( + weights = NULL, + directed = TRUE, + mode = c("all", "out", "in", "total") + ), + head_args = c("graph", "vids"), + fn_name = "local_efficiency" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + local_efficiency_impl( graph = graph, vids = vids, @@ -93,13 +149,41 @@ local_efficiency <- function( } #' @rdname global_efficiency +#' @inheritParams rlang::args_dots_empty #' @export average_local_efficiency <- function( graph, + ..., weights = NULL, directed = TRUE, mode = c("all", "out", "in", "total") ) { + # BEGIN GENERATED ARG_HANDLE: average_local_efficiency, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(weights = weights, directed = directed, mode = mode), + recover_new = c("weights", "directed", "mode"), + recover_old = c("weights", "directed", "mode"), + match_names = c("weights", "directed", "mode"), + match_to = c("weights", "directed", "mode"), + defaults = list( + weights = NULL, + directed = TRUE, + mode = c("all", "out", "in", "total") + ), + head_args = c("graph"), + fn_name = "average_local_efficiency" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + average_local_efficiency_impl( graph = graph, weights = weights, diff --git a/R/similarity.R b/R/similarity.R index d303a4a5b3e..481ba60ff3c 100644 --- a/R/similarity.R +++ b/R/similarity.R @@ -27,6 +27,7 @@ #' #' @param graph The input graph. #' @param vids The vertex IDs for which the similarity is calculated. +#' @inheritParams rlang::args_dots_empty #' @param mode The type of neighboring vertices to use for the calculation, #' possible values: \sQuote{`out`}, \sQuote{`in`}, #' \sQuote{`all`}. @@ -52,6 +53,7 @@ similarity <- function( graph, vids = V(graph), + ..., mode = c( "all", "out", @@ -65,6 +67,32 @@ similarity <- function( "invlogweighted" ) ) { + # BEGIN GENERATED ARG_HANDLE: similarity, do not edit, see tools/generate-migrations.R + if (...length() > 0L) { + .arg_handle <- migrate_recover_args( + list(...), + current = list(mode = mode, loops = loops, method = method), + recover_new = c("mode", "loops", "method"), + recover_old = c("mode", "loops", "method"), + match_names = c("mode", "loops", "method"), + match_to = c("mode", "loops", "method"), + defaults = list( + mode = c("all", "out", "in", "total"), + loops = FALSE, + method = c("jaccard", "dice", "invlogweighted") + ), + head_args = c("graph", "vids"), + fn_name = "similarity" + ) + list2env(.arg_handle$values, environment()) + lifecycle::deprecate_soft( + "3.0.0", + what = I(.arg_handle$what), + details = .arg_handle$details + ) + } + # END GENERATED ARG_HANDLE + method <- igraph_match_arg(method) if (method == "jaccard") { similarity_jaccard_impl( diff --git a/man/assortativity.Rd b/man/assortativity.Rd index 50eda0190ce..dde0c7e7940 100644 --- a/man/assortativity.Rd +++ b/man/assortativity.Rd @@ -17,9 +17,9 @@ assortativity( types2 = NULL ) -assortativity_nominal(graph, types, directed = TRUE, normalized = TRUE) +assortativity_nominal(graph, types, ..., directed = TRUE, normalized = TRUE) -assortativity_degree(graph, directed = TRUE) +assortativity_degree(graph, ..., directed = TRUE) } \arguments{ \item{graph}{The input graph, it can be directed or undirected.} diff --git a/man/global_efficiency.Rd b/man/global_efficiency.Rd index 9e7df3c6e35..b24157c080d 100644 --- a/man/global_efficiency.Rd +++ b/man/global_efficiency.Rd @@ -6,11 +6,12 @@ \alias{average_local_efficiency} \title{Efficiency of a graph} \usage{ -global_efficiency(graph, weights = NULL, directed = TRUE) +global_efficiency(graph, ..., weights = NULL, directed = TRUE) local_efficiency( graph, vids = V(graph), + ..., weights = NULL, directed = TRUE, mode = c("all", "out", "in", "total") @@ -18,6 +19,7 @@ local_efficiency( average_local_efficiency( graph, + ..., weights = NULL, directed = TRUE, mode = c("all", "out", "in", "total") @@ -26,6 +28,8 @@ average_local_efficiency( \arguments{ \item{graph}{The graph to analyze.} +\item{...}{These dots are for future extensions and must be empty.} + \item{weights}{The edge weights. All edge weights must be non-negative; additionally, no edge weight may be NaN. If it is \code{NULL} (the default) and the graph has a \code{weight} edge attribute, then it is used automatically.} diff --git a/man/similarity.Rd b/man/similarity.Rd index 117c9e0a1b7..2b254e0d5a6 100644 --- a/man/similarity.Rd +++ b/man/similarity.Rd @@ -7,6 +7,7 @@ similarity( graph, vids = V(graph), + ..., mode = c("all", "out", "in", "total"), loops = FALSE, method = c("jaccard", "dice", "invlogweighted") @@ -17,6 +18,8 @@ similarity( \item{vids}{The vertex IDs for which the similarity is calculated.} +\item{...}{These dots are for future extensions and must be empty.} + \item{mode}{The type of neighboring vertices to use for the calculation, possible values: \sQuote{\code{out}}, \sQuote{\verb{in}}, \sQuote{\code{all}}.} diff --git a/tests/testthat/test-assortativity.R b/tests/testthat/test-assortativity.R index 516c093d4f0..82f01257bfa 100644 --- a/tests/testthat/test-assortativity.R +++ b/tests/testthat/test-assortativity.R @@ -83,3 +83,47 @@ test_that("nominal assortativity works with character types", { result_numeric <- assortativity_nominal(g, types = numeric_from_char) expect_equal(result3, result_numeric) }) + +# ---- ellipsis migration: argument coverage ---------------------------- + +test_that("assortativity_degree() covers directed", { + g <- make_star(6, mode = "out") + + # A star is perfectly disassortative when treated as undirected, + # while the directed variant is undefined for an out-star. + expect_equal(assortativity_degree(g, directed = FALSE), -1) + expect_true(is.nan(assortativity_degree(g))) +}) + +test_that("assortativity_degree() recovers legacy positional arguments", { + g <- make_star(6, mode = "out") + + lifecycle::expect_deprecated( + res <- assortativity_degree(g, FALSE) + ) + expect_identical(res, assortativity_degree(g, directed = FALSE)) +}) + +test_that("assortativity_nominal() covers directed and normalized", { + # Two directed triangles joined by a single bridge edge. + g <- make_graph(c(1, 2, 2, 3, 3, 1, 4, 5, 5, 6, 6, 4, 3, 4), directed = TRUE) + types <- c(1, 1, 1, 2, 2, 2) + + # The non-normalized undirected value is the modularity of the two groups. + expect_equal( + assortativity_nominal(g, types, directed = FALSE, normalized = FALSE), + 5 / 14 + ) + # Edge directions change the non-normalized value under the directed default. + expect_equal(assortativity_nominal(g, types, normalized = FALSE), 18 / 49) +}) + +test_that("assortativity_nominal() recovers legacy positional arguments", { + g <- make_graph(c(1, 2, 2, 3, 3, 1, 4, 5, 5, 6, 6, 4, 3, 4), directed = TRUE) + types <- c(1, 1, 1, 2, 2, 2) + + lifecycle::expect_deprecated( + res <- assortativity_nominal(g, types, FALSE) + ) + expect_identical(res, assortativity_nominal(g, types, directed = FALSE)) +}) diff --git a/tests/testthat/test-efficiency.R b/tests/testthat/test-efficiency.R index 78b525f2390..0d05c2ff558 100644 --- a/tests/testthat/test-efficiency.R +++ b/tests/testthat/test-efficiency.R @@ -28,3 +28,66 @@ test_that("local_efficiency works", { expect_equal(as.vector(local_efficiency(g, mode = "in")), rep(0, vcount(g))) expect_equal(as.vector(local_efficiency(g, mode = "out")), rep(0, vcount(g))) }) + +# ---- ellipsis migration: argument coverage ---------------------------- + +test_that("global_efficiency() covers weights and directed", { + g <- make_ring(3, directed = TRUE) + + # As an undirected weighted triangle, the pairwise distances are 1, 2 and 3. + expect_equal( + global_efficiency(g, weights = c(1, 2, 4), directed = FALSE), + 11 / 18 + ) +}) + +test_that("global_efficiency() recovers legacy positional arguments", { + g <- make_ring(3, directed = TRUE) + + lifecycle::expect_deprecated( + res <- global_efficiency(g, c(1, 2, 4)) + ) + expect_identical(res, global_efficiency(g, weights = c(1, 2, 4))) +}) + +test_that("local_efficiency() covers vids, weights and directed", { + g <- make_ring(4, directed = TRUE) + + # As an undirected weighted square with weights 1:4, + # the neighbors of vertex 2 reconnect at distance 7 + # and those of vertex 4 at distance 3. + expect_equal( + local_efficiency(g, vids = c(2, 4), weights = 1:4, directed = FALSE), + c(1 / 7, 1 / 3) + ) +}) + +test_that("local_efficiency() recovers legacy positional arguments", { + g <- make_ring(4, directed = TRUE) + + lifecycle::expect_deprecated( + res <- local_efficiency(g, V(g), 1:4) + ) + expect_identical(res, local_efficiency(g, V(g), weights = 1:4)) +}) + +test_that("average_local_efficiency() covers weights, directed and mode", { + g <- make_ring(4, directed = TRUE) + + # Mean of the undirected weighted local efficiencies 1/5, 1/7, 1/5 and 1/3. + expect_equal( + average_local_efficiency(g, weights = 1:4, directed = FALSE), + 23 / 105 + ) + # Each vertex has a single out-neighbor, so all local efficiencies are zero. + expect_equal(average_local_efficiency(g, mode = "out"), 0) +}) + +test_that("average_local_efficiency() recovers legacy positional arguments", { + g <- make_ring(4, directed = TRUE) + + lifecycle::expect_deprecated( + res <- average_local_efficiency(g, 1:4) + ) + expect_identical(res, average_local_efficiency(g, weights = 1:4)) +}) diff --git a/tests/testthat/test-similarity.R b/tests/testthat/test-similarity.R index f75eaaae7c0..3e741a0055d 100644 --- a/tests/testthat/test-similarity.R +++ b/tests/testthat/test-similarity.R @@ -28,3 +28,34 @@ test_that("similarity works", { ) expect_equal(sim_jac, sim_jac_true) }) + +# ---- ellipsis migration: argument coverage ---------------------------- + +test_that("similarity() covers vids, mode, loops and method", { + g <- make_ring(5, directed = TRUE) + + # With loops, the out-neighbor set of ring vertex i is {i, i + 1}, + # so consecutive vertices share exactly one of their two out-neighbors. + sim <- similarity( + g, + vids = V(g)[1:3], + mode = "out", + loops = TRUE, + method = "dice" + ) + expect_equal( + sim, + matrix(c(1, 0.5, 0, 0.5, 1, 0.5, 0, 0.5, 1), nrow = 3L) + ) +}) + +test_that("similarity() recovers legacy positional arguments", { + g <- make_ring(5, directed = TRUE) + + # Without loops, distinct out-neighbor sets make this the identity matrix. + lifecycle::expect_deprecated( + res <- similarity(g, V(g)[1:3], "out") + ) + expect_identical(res, similarity(g, V(g)[1:3], mode = "out")) + expect_equal(res, diag(3)) +}) diff --git a/tools/migrations/similarity-efficiency.R b/tools/migrations/similarity-efficiency.R new file mode 100644 index 00000000000..c6f1ad256a3 --- /dev/null +++ b/tools/migrations/similarity-efficiency.R @@ -0,0 +1,85 @@ +# Argument-signature migrations: similarity-efficiency +# Schema: see tools/migrations/README.md. Regenerate with: +# Rscript tools/generate-migrations.R + +migrations <- list( + assortativity_degree = list( + old = function(graph, directed) {}, + new = function( + graph, + ..., + directed = TRUE + ) {}, + when = "3.0.0" + ), + + assortativity_nominal = list( + old = function(graph, types, directed, normalized) {}, + new = function( + graph, + types, + ..., + directed = TRUE, + normalized = TRUE + ) {}, + when = "3.0.0" + ), + + average_local_efficiency = list( + old = function(graph, weights, directed, mode) {}, + new = function( + graph, + ..., + weights = NULL, + directed = TRUE, + mode = c("all", "out", "in", "total") + ) {}, + when = "3.0.0" + ), + + global_efficiency = list( + old = function(graph, weights, directed) {}, + new = function( + graph, + ..., + weights = NULL, + directed = TRUE + ) {}, + when = "3.0.0" + ), + + local_efficiency = list( + old = function(graph, vids, weights, directed, mode) {}, + new = function( + graph, + vids = V(graph), + ..., + weights = NULL, + directed = TRUE, + mode = c("all", "out", "in", "total") + ) {}, + when = "3.0.0" + ), + + similarity = list( + old = function(graph, vids, mode, loops, method) {}, + new = function( + graph, + vids = V(graph), + ..., + mode = c( + "all", + "out", + "in", + "total" + ), + loops = FALSE, + method = c( + "jaccard", + "dice", + "invlogweighted" + ) + ) {}, + when = "3.0.0" + ) +)