diff --git a/R/adjacency.R b/R/adjacency.R index 3eaf74a2d27..0f658e5a3e3 100644 --- a/R/adjacency.R +++ b/R/adjacency.R @@ -542,7 +542,7 @@ graph.adjacency.sparse <- function( res <- make_empty_graph(n = vc, directed = (mode == "directed")) weight <- list(el[, 3]) names(weight) <- weighted - res <- add_edges(res, edges = t(as.matrix(el[, 1:2])), attr = weight) + res <- add_edges(res, edges = c(t(as.matrix(el[, 1:2]))), attr = weight) } else { if (max(el[, 3]) == 1) { edges <- as.vector(t(el[, 1:2])) diff --git a/R/conversion.R b/R/conversion.R index c978a051e50..665d84093f1 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -1748,7 +1748,7 @@ graph_from_data_frame <- function(d, directed = TRUE, vertices = NULL) { } # add the edges - g <- add_edges(g, edges, attr = attrs) + g <- add_edges(g, c(edges), attr = attrs) g } @@ -1800,7 +1800,7 @@ graph_from_edgelist <- function(el, directed = TRUE) { V(res)$name <- names } else { ## normal edge list - res <- make_graph(t(el), directed = directed) + res <- make_graph(c(t(el)), directed = directed) } } diff --git a/R/incidence.R b/R/incidence.R index 886aa1a9f8c..657f7c3c52a 100644 --- a/R/incidence.R +++ b/R/incidence.R @@ -107,7 +107,7 @@ graph_incidence_build <- function( res <- make_empty_graph(n = num_rows + num_cols, directed = directed) weight_attr <- list(el[, 3]) names(weight_attr) <- weighted - res <- add_edges(res, edges = t(el[, 1:2]), attr = weight_attr) + res <- add_edges(res, edges = c(t(el[, 1:2])), attr = weight_attr) } else { # Handle unweighted edges, replicating rows for multiple edges el <- el[rep(seq_len(nrow(el)), times = el[, 3]), 1:2] diff --git a/R/indexing.R b/R/indexing.R index 6329bd5d5c7..167e1e93258 100644 --- a/R/indexing.R +++ b/R/indexing.R @@ -521,7 +521,7 @@ expand.grid.unordered <- function(i, j, loops = FALSE, directed = FALSE) { ## Addition or update of an attribute (or both) ids <- get_edge_ids(x, c(rbind(from, to))) if (any(ids == 0)) { - x <- add_edges(x, rbind(from[ids == 0], to[ids == 0])) + x <- add_edges(x, c(rbind(from[ids == 0], to[ids == 0]))) } if (!is.null(attr)) { ids <- get_edge_ids(x, c(rbind(from, to))) diff --git a/R/interface.R b/R/interface.R index bc528bf78d0..b3eb06eba46 100644 --- a/R/interface.R +++ b/R/interface.R @@ -113,8 +113,10 @@ add.edges <- function(graph, edges, ..., attr = list()) { #' their values for the original edges of the graph are set to `NA`. #' #' @param graph The input graph -#' @param edges The edges to add, a vertex sequence with even number -#' of vertices. +#' @param edges The edges to add. Either a two-column matrix or data frame, each +#' row giving the two endpoints of an edge, or a vertex sequence with an even +#' number of vertices, interpreted pairwise (the first and second vertices +#' form the first edge, the third and fourth the second edge, etc.). #' @param ... Additional arguments, they must be named, #' and they will be added as edge attributes, for the newly added #' edges. See also details below. @@ -149,6 +151,10 @@ add_edges <- function(graph, edges, ..., attr = list()) { cli::cli_abort("All attributes must be named.") } + if (is.data.frame(edges) || inherits(edges, "matrix")) { + edges <- el_to_vec(edges, arg = "edges", fn = "add_edges") + } + edges.orig <- ecount(graph) on.exit(.Call(Rx_igraph_finalizer)) graph <- .Call( @@ -245,7 +251,8 @@ add_vertices <- function(graph, nv, ..., attr = list()) { #' @param edges The edges to remove, specified as an edge sequence. Typically #' this is either a numeric vector containing edge IDs, or a character vector #' containing the IDs or names of the source and target vertices, separated by -#' `|` +#' `|`. A two-column matrix or data frame is also accepted, each row giving the +#' two endpoints of an edge to remove. #' @return The graph, with the edges removed. #' #' @family functions for manipulating graph structure @@ -264,6 +271,10 @@ add_vertices <- function(graph, nv, ..., attr = list()) { #' g <- delete_edges(g, get_edge_ids(g, c(1, 5, 4, 5))) #' g delete_edges <- function(graph, edges) { + ensure_igraph(graph) + if (is.data.frame(edges) || inherits(edges, "matrix")) { + edges <- get_edge_ids(graph, edges, error = TRUE) + } delete_edges_impl( graph = graph, edges = edges @@ -455,7 +466,12 @@ get.edges <- function(graph, es) { ends(graph, es, names = FALSE) } -el_to_vec <- function(x, call = rlang::caller_env()) { +el_to_vec <- function( + x, + arg = "vp", + fn = "get_edge_ids", + call = rlang::caller_env() +) { if (is.data.frame(x)) { if (typeof(x[[1]]) == typeof(x[[2]])) { c(rbind(x[[1]], x[[2]])) @@ -471,26 +487,31 @@ el_to_vec <- function(x, call = rlang::caller_env()) { if (nrow == 2 && ncol == 2) { lifecycle::deprecate_stop( "2.1.5", - "get_edge_ids(vp = 'is not allowed to be a 2 times 2 matrix')" + paste0(fn, "(", arg, " = 'is not allowed to be a 2 times 2 matrix')") ) } else if (nrow == 2) { lifecycle::deprecate_stop( "2.1.5", - "get_edge_ids(vp = 'supplied as a matrix should be a n times 2 matrix, not 2 times n')", + paste0( + fn, + "(", + arg, + " = 'supplied as a matrix should be a n times 2 matrix, not 2 times n')" + ), details = "either transpose the matrix with t() or convert it to a data.frame with two columns." ) } else if (ncol == 2) { c(t(x)) } else { cli::cli_abort( - "{.args vp} was supplied as a {dimx[1]} times {dimx[2]} matrix. Only n times 2 matrices are allowed" + "{.args {arg}} was supplied as a {dimx[1]} times {dimx[2]} matrix. Only n times 2 matrices are allowed" ) } } else if (is.vector(x)) { x } else { cli::cli_abort( - "Only two-column data.frames and matrices, and vectors are allowed for {.args vp}", + "Only two-column data.frames and matrices, and vectors are allowed for {.args {arg}}", call = call ) } diff --git a/R/iterators.R b/R/iterators.R index 097fd8a9aeb..f40463a8388 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -327,7 +327,9 @@ unsafe_create_es <- function(graph, idx, es = NULL) { #' @param graph The graph. #' @param P A list of vertices to select edges via pairs of vertices. #' The first and second vertices select the first edge, the third -#' and fourth the second, etc. +#' and fourth the second, etc. Alternatively, a two-column matrix or +#' data frame can be given, each row defining the two endpoints of an +#' edge to select. #' @param path A list of vertices, to select edges along a path. #' Note that this only works reliable for simple graphs. If the graph #' has multiple edges, one of them will be chosen arbitrarily to @@ -361,6 +363,9 @@ E <- function(graph, P = NULL, path = NULL, directed = TRUE) { res <- seq_len(ec) res <- set_complete_iterator(res) } else if (!is.null(P)) { + if (is.data.frame(P) || inherits(P, "matrix")) { + P <- el_to_vec(P, arg = "P", fn = "E") + } on.exit(.Call(Rx_igraph_finalizer)) res <- .Call( Rx_igraph_es_pairs, diff --git a/R/make.R b/R/make.R index 8e70a149812..ca63f0d8bf4 100644 --- a/R/make.R +++ b/R/make.R @@ -1381,7 +1381,8 @@ with_graph_ <- function(...) { #' from the first element to the second, the second edge from the third #' to the fourth, etc. For a numeric vector, these are interpreted #' as internal vertex IDs. For character vectors, they are interpreted -#' as vertex names. +#' as vertex names. Alternatively, a two-column matrix or data frame +#' can be given, each row defining the two endpoints of an edge. #' #' Alternatively, this can be a character scalar, the name of a #' notable graph. See Notable graphs below. The name is case @@ -1467,6 +1468,10 @@ make_graph <- function( directed <- dir } + if (is.data.frame(edges) || inherits(edges, "matrix")) { + edges <- el_to_vec(edges, arg = "edges", fn = "make_graph") + } + if (is.character(edges) && length(edges) == 1) { if (!missing(n)) { cli::cli_warn("{.arg n} is ignored for the {.str {edges}} graph.") diff --git a/R/operators.R b/R/operators.R index 8782cfb0612..4cd62218c7b 100644 --- a/R/operators.R +++ b/R/operators.R @@ -898,11 +898,14 @@ compose <- function(g1, g2, byname = "auto") { #' `edge()` (or `edges()`) are concatenated, and then passed to #' [add_edges()]. They are interpreted as pairs of vertex IDs, #' and an edge will added between each pair. Named arguments will be -#' used as edge attributes for the new edges. +#' used as edge attributes for the new edges. Alternatively, a single +#' two-column matrix or data frame of edge endpoints can be passed as the +#' only unnamed argument. #' #' When deleting edges via `-`, all arguments of `edge()` (or #' `edges()`) are concatenated via `c()` and passed to -#' [delete_edges()]. +#' [delete_edges()]. A single two-column matrix or data frame of edge +#' endpoints can also be passed as the only argument. #' #' @param ... See details below. #' @return A special object that can be used with together with @@ -1139,13 +1142,27 @@ path <- function(...) { ## Adding edges, possibly with attributes ## Non-named arguments define the edges if (is.null(names(e2))) { - toadd <- unlist(e2, recursive = FALSE) + unnamed <- e2 attr <- list() } else { - toadd <- unlist(e2[!nzchar(names(e2))]) + unnamed <- e2[!nzchar(names(e2))] attr <- e2[nzchar(names(e2))] } - res <- add_edges(e1, as_igraph_vs(e1, toadd), attr = attr) + if ( + length(unnamed) == 1 && + (is.data.frame(unnamed[[1]]) || inherits(unnamed[[1]], "matrix")) + ) { + ## A single two-column matrix or data frame of edge endpoints; + ## let add_edges() normalize it via el_to_vec(). + res <- add_edges(e1, unnamed[[1]], attr = attr) + } else { + if (is.null(names(e2))) { + toadd <- unlist(unnamed, recursive = FALSE) + } else { + toadd <- unlist(unnamed) + } + res <- add_edges(e1, as_igraph_vs(e1, toadd), attr = attr) + } } else if (inherits(e2, "igraph.vertex")) { ## Adding vertices, possibly with attributes ## If there is a single unnamed argument, that contains the vertex names @@ -1250,7 +1267,14 @@ path <- function(...) { } else if (inherits(e2, "igraph.vertex")) { res <- delete_vertices(e1, unlist(e2, recursive = FALSE)) } else if (inherits(e2, "igraph.edge")) { - res <- delete_edges(e1, unlist(e2, recursive = FALSE)) + if ( + length(e2) == 1 && (is.data.frame(e2[[1]]) || inherits(e2[[1]], "matrix")) + ) { + todel <- e2[[1]] + } else { + todel <- unlist(e2, recursive = FALSE) + } + res <- delete_edges(e1, todel) } else if (inherits(e2, "igraph.path")) { todel <- unlist(e2, recursive = FALSE) lt <- length(todel) diff --git a/R/structural-properties.R b/R/structural-properties.R index 9182950592e..4a81a3512f0 100644 --- a/R/structural-properties.R +++ b/R/structural-properties.R @@ -1182,7 +1182,7 @@ degree_distribution <- function(graph, cumulative = FALSE, ...) { #' 9, 10, 4 #' ) #' ) -#' g2 <- add_edges(make_empty_graph(10), t(el[, 1:2]), weight = el[, 3]) +#' g2 <- add_edges(make_empty_graph(10), el[, 1:2], weight = el[, 3]) #' distances(g2, mode = "out") #' distances <- function( diff --git a/man/E.Rd b/man/E.Rd index fe4b84517c8..e06c5011348 100644 --- a/man/E.Rd +++ b/man/E.Rd @@ -11,7 +11,9 @@ E(graph, P = NULL, path = NULL, directed = TRUE) \item{P}{A list of vertices to select edges via pairs of vertices. The first and second vertices select the first edge, the third -and fourth the second, etc.} +and fourth the second, etc. Alternatively, a two-column matrix or +data frame can be given, each row defining the two endpoints of an +edge to select.} \item{path}{A list of vertices, to select edges along a path. Note that this only works reliable for simple graphs. If the graph diff --git a/man/add.edges.Rd b/man/add.edges.Rd index 0c2fc925a37..c6505b1af7d 100644 --- a/man/add.edges.Rd +++ b/man/add.edges.Rd @@ -9,8 +9,10 @@ add.edges(graph, edges, ..., attr = list()) \arguments{ \item{graph}{The input graph} -\item{edges}{The edges to add, a vertex sequence with even number -of vertices.} +\item{edges}{The edges to add. Either a two-column matrix or data frame, each +row giving the two endpoints of an edge, or a vertex sequence with an even +number of vertices, interpreted pairwise (the first and second vertices +form the first edge, the third and fourth the second edge, etc.).} \item{...}{Additional arguments, they must be named, and they will be added as edge attributes, for the newly added diff --git a/man/add_edges.Rd b/man/add_edges.Rd index 01859f4dde3..47a0a802b38 100644 --- a/man/add_edges.Rd +++ b/man/add_edges.Rd @@ -9,8 +9,10 @@ add_edges(graph, edges, ..., attr = list()) \arguments{ \item{graph}{The input graph} -\item{edges}{The edges to add, a vertex sequence with even number -of vertices.} +\item{edges}{The edges to add. Either a two-column matrix or data frame, each +row giving the two endpoints of an edge, or a vertex sequence with an even +number of vertices, interpreted pairwise (the first and second vertices +form the first edge, the third and fourth the second edge, etc.).} \item{...}{Additional arguments, they must be named, and they will be added as edge attributes, for the newly added diff --git a/man/delete.edges.Rd b/man/delete.edges.Rd index 3c0a70138f7..aa9a34cefe8 100644 --- a/man/delete.edges.Rd +++ b/man/delete.edges.Rd @@ -12,7 +12,8 @@ delete.edges(graph, edges) \item{edges}{The edges to remove, specified as an edge sequence. Typically this is either a numeric vector containing edge IDs, or a character vector containing the IDs or names of the source and target vertices, separated by -\code{|}} +\code{|}. A two-column matrix or data frame is also accepted, each row giving the +two endpoints of an edge to remove.} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} diff --git a/man/delete_edges.Rd b/man/delete_edges.Rd index ca7508c1fe2..f5cf4569f19 100644 --- a/man/delete_edges.Rd +++ b/man/delete_edges.Rd @@ -12,7 +12,8 @@ delete_edges(graph, edges) \item{edges}{The edges to remove, specified as an edge sequence. Typically this is either a numeric vector containing edge IDs, or a character vector containing the IDs or names of the source and target vertices, separated by -\code{|}} +\code{|}. A two-column matrix or data frame is also accepted, each row giving the +two endpoints of an edge to remove.} } \value{ The graph, with the edges removed. diff --git a/man/distances.Rd b/man/distances.Rd index 1a7e8811000..51ef53beb15 100644 --- a/man/distances.Rd +++ b/man/distances.Rd @@ -284,7 +284,7 @@ el <- matrix( 9, 10, 4 ) ) -g2 <- add_edges(make_empty_graph(10), t(el[, 1:2]), weight = el[, 3]) +g2 <- add_edges(make_empty_graph(10), el[, 1:2], weight = el[, 3]) distances(g2, mode = "out") } diff --git a/man/edge.Rd b/man/edge.Rd index 41ea5e5f37c..7bc4d27d5ea 100644 --- a/man/edge.Rd +++ b/man/edge.Rd @@ -27,11 +27,14 @@ When adding edges via \code{+}, all unnamed arguments of \code{edge()} (or \code{edges()}) are concatenated, and then passed to \code{\link[=add_edges]{add_edges()}}. They are interpreted as pairs of vertex IDs, and an edge will added between each pair. Named arguments will be -used as edge attributes for the new edges. +used as edge attributes for the new edges. Alternatively, a single +two-column matrix or data frame of edge endpoints can be passed as the +only unnamed argument. When deleting edges via \code{-}, all arguments of \code{edge()} (or \code{edges()}) are concatenated via \code{c()} and passed to -\code{\link[=delete_edges]{delete_edges()}}. +\code{\link[=delete_edges]{delete_edges()}}. A single two-column matrix or data frame of edge +endpoints can also be passed as the only argument. } \examples{ g <- make_ring(10) \%>\% diff --git a/man/graph.Rd b/man/graph.Rd index 1502dd379af..9086faf4e58 100644 --- a/man/graph.Rd +++ b/man/graph.Rd @@ -19,7 +19,8 @@ graph( from the first element to the second, the second edge from the third to the fourth, etc. For a numeric vector, these are interpreted as internal vertex IDs. For character vectors, they are interpreted -as vertex names. +as vertex names. Alternatively, a two-column matrix or data frame +can be given, each row defining the two endpoints of an edge. Alternatively, this can be a character scalar, the name of a notable graph. See Notable graphs below. The name is case diff --git a/man/graph.famous.Rd b/man/graph.famous.Rd index 52c5eff3fb7..62dde359897 100644 --- a/man/graph.famous.Rd +++ b/man/graph.famous.Rd @@ -19,7 +19,8 @@ graph.famous( from the first element to the second, the second edge from the third to the fourth, etc. For a numeric vector, these are interpreted as internal vertex IDs. For character vectors, they are interpreted -as vertex names. +as vertex names. Alternatively, a two-column matrix or data frame +can be given, each row defining the two endpoints of an edge. Alternatively, this can be a character scalar, the name of a notable graph. See Notable graphs below. The name is case diff --git a/man/make_graph.Rd b/man/make_graph.Rd index 37c8cb01f0d..b61b3ae588b 100644 --- a/man/make_graph.Rd +++ b/man/make_graph.Rd @@ -32,7 +32,8 @@ undirected_graph(...) from the first element to the second, the second edge from the third to the fourth, etc. For a numeric vector, these are interpreted as internal vertex IDs. For character vectors, they are interpreted -as vertex names. +as vertex names. Alternatively, a two-column matrix or data frame +can be given, each row defining the two endpoints of an edge. Alternatively, this can be a character scalar, the name of a notable graph. See Notable graphs below. The name is case diff --git a/tests/testthat/test-conversion.R b/tests/testthat/test-conversion.R index 9d732b09436..29fc1011d3d 100644 --- a/tests/testthat/test-conversion.R +++ b/tests/testthat/test-conversion.R @@ -601,7 +601,7 @@ test_that("as_adj_list works when return.vs.es is FALSE", { test_that("as_edgelist works", { g <- sample_gnp(100, 3 / 100) el <- as_edgelist(g) - g2 <- make_graph(t(el), n = vcount(g), dir = FALSE) + g2 <- make_graph(el, n = vcount(g), dir = FALSE) expect_isomorphic(g, g2) }) diff --git a/tests/testthat/test-interface.R b/tests/testthat/test-interface.R index 9be76e65bc9..a7ee4742c3a 100644 --- a/tests/testthat/test-interface.R +++ b/tests/testthat/test-interface.R @@ -44,6 +44,29 @@ test_that("add_edges signals error for zero vertex ids", { expect_error(add_edges(g, c(0, 5, 0, 10, 5, 10)), "Invalid vertex") }) +test_that("add_edges accepts a two-column matrix or data frame (#1827)", { + g <- make_empty_graph(5) + mat <- matrix(c(1, 2, 2, 3, 3, 4), ncol = 2, byrow = TRUE) + g_mat <- add_edges(g, mat) + expect_equal(as_edgelist(g_mat), mat) + + df <- data.frame(from = c(1, 2, 3), to = c(2, 3, 4)) + g_df <- add_edges(g, df) + expect_equal(as_edgelist(g_df), mat) +}) + +test_that("add_edges still accepts vectors and vertex sequences", { + g <- make_empty_graph(5) + expect_ecount(add_edges(g, c(1, 2, 2, 3)), 2) + expect_ecount(add_edges(g, V(g)[c(1, 2, 3, 4)]), 2) +}) + +test_that("add_edges rejects 2xn and 2x2 matrices (#1827)", { + g <- make_empty_graph(5) + lifecycle::expect_defunct(add_edges(g, matrix(c(1, 2, 3, 4), nrow = 2))) + lifecycle::expect_defunct(add_edges(g, matrix(c(1, 2, 1, 3, 1, 4), nrow = 2))) +}) + test_that("add_vertices works", { g <- graph_from_literal(A - B - C - D - E) nv <- 4 @@ -171,6 +194,31 @@ test_that("delete_edges works", { expect_equal(as.matrix(g2[]), expected_matrix) }) +test_that("delete_edges accepts a two-column matrix or data frame (#1827)", { + g <- make_ring(5) + mat <- matrix(c(1, 2, 2, 3, 3, 4), ncol = 2, byrow = TRUE) + expect_ecount(delete_edges(g, mat), 2) + + df <- data.frame(from = c(1, 2, 3), to = c(2, 3, 4)) + expect_ecount(delete_edges(g, df), 2) +}) + +test_that("delete_edges preserves vector semantics (edge ids and a|b)", { + g <- make_ring(5) + expect_ecount(delete_edges(g, c(1, 2)), 3) + expect_ecount(delete_edges(g, "1|2"), 4) +}) + +test_that("delete_edges round-trips with as_edgelist (#550)", { + g <- make_ring(5) + expect_ecount(delete_edges(g, as_edgelist(g)), 0) +}) + +test_that("delete_edges errors on a vertex pair with no edge (#1827)", { + g <- make_ring(5) + expect_error(delete_edges(g, matrix(c(1, 3), ncol = 2))) +}) + test_that("ends works", { g <- sample_gnp(100, 3 / 100) edges <- unlist(lapply(seq_len(ecount(g)), ends, graph = g)) diff --git a/tests/testthat/test-iterators.R b/tests/testthat/test-iterators.R index a65fcf1b56f..4f4454ee7dc 100644 --- a/tests/testthat/test-iterators.R +++ b/tests/testthat/test-iterators.R @@ -111,6 +111,15 @@ test_that("E(g) returns complete iterator, completeness is lost with next subset expect_false(is_complete_iterator(E(g, path = 1:4))) }) +test_that("E(g, P=) accepts a two-column matrix or data frame (#1827)", { + g <- make_ring(10) + vec <- E(g, P = c(1, 2, 4, 5, 6, 7)) + mat <- E(g, P = matrix(c(1, 2, 4, 5, 6, 7), ncol = 2, byrow = TRUE)) + df <- E(g, P = data.frame(from = c(1, 4, 6), to = c(2, 5, 7))) + expect_equal(as.vector(mat), as.vector(vec)) + expect_equal(as.vector(df), as.vector(vec)) +}) + test_that("ids change when updating the graph", { g <- make_ring(10) diff --git a/tests/testthat/test-make.R b/tests/testthat/test-make.R index 245ee149e93..9b471620cef 100644 --- a/tests/testthat/test-make.R +++ b/tests/testthat/test-make.R @@ -10,6 +10,25 @@ test_that("make_ works, order of arguments does not matter", { expect_identical_graphs(g0, g3) }) +test_that("make_graph() accepts a two-column matrix or data frame (#1827)", { + mat <- matrix(c(1, 2, 2, 3, 3, 4), ncol = 2, byrow = TRUE) + g_mat <- make_graph(mat, directed = FALSE) + expect_equal(as_edgelist(g_mat), mat) + + df <- data.frame(from = c(1, 2, 3), to = c(2, 3, 4)) + g_df <- make_graph(df, directed = FALSE) + expect_equal(as_edgelist(g_df), mat) + + # round-trips an edge list without manual transposition (#550) + g <- make_ring(6) + expect_isomorphic(make_graph(as_edgelist(g), directed = FALSE), g) +}) + +test_that("make_graph() rejects 2xn and 2x2 matrices (#1827)", { + lifecycle::expect_defunct(make_graph(matrix(c(1, 2, 3, 4), nrow = 2))) + lifecycle::expect_defunct(make_graph(matrix(c(1, 2, 1, 3, 1, 4), nrow = 2))) +}) + test_that("make_ works with n parameter", { g0 <- make_undirected_graph(1:10, n = 15) expect_vcount(g0, 15) diff --git a/tests/testthat/test-operators.R b/tests/testthat/test-operators.R index b79725b8a90..44fc9419f8a 100644 --- a/tests/testthat/test-operators.R +++ b/tests/testthat/test-operators.R @@ -278,6 +278,20 @@ test_that("vertices() errors on duplicate attribute names", { ) }) +test_that("edge()/edges() accept a two-column matrix or data frame (#1827)", { + mat <- matrix(c(1, 2, 2, 3, 3, 4), ncol = 2, byrow = TRUE) + g <- make_empty_graph(5) + edges(mat) + expect_ecount(g, 3) + expect_equal(as_edgelist(g), mat) + + df <- data.frame(from = c(1, 2, 3), to = c(2, 3, 4)) + g <- make_empty_graph(5) + edges(df) + expect_equal(as_edgelist(g), mat) + + g <- make_ring(5) - edges(mat) + expect_ecount(g, 2) +}) + test_that("infix operators work", { g <- make_ring(10) V(g)$name <- letters[1:10]