From b5bb8d12f1410c92ccc3bec132937da1d859044f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:17:08 +0000 Subject: [PATCH 1/5] Initial plan From 4a8efce8d3545fb07a16fb98a0b3a410d8aef7b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:23:56 +0000 Subject: [PATCH 2/5] Implement network methods for networkLite Co-authored-by: krivit <15682462+krivit@users.noreply.github.com> --- NAMESPACE | 7 + R/network_methods.R | 318 ++++++++++++++++++++++++++ tests/testthat/test-network-methods.R | 201 ++++++++++++++++ 3 files changed, 526 insertions(+) create mode 100644 R/network_methods.R create mode 100644 tests/testthat/test-network-methods.R diff --git a/NAMESPACE b/NAMESPACE index cbd2816..12abb4a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,16 +24,23 @@ S3method(delete.edges,networkLite) S3method(delete.network.attribute,networkLite) S3method(delete.vertex.attribute,networkLite) S3method(delete.vertices,networkLite) +S3method(get.dyads.eids,networkLite) S3method(get.edge.attribute,networkLite) S3method(get.edge.value,networkLite) +S3method(get.edgeIDs,networkLite) +S3method(get.edges,networkLite) S3method(get.inducedSubgraph,networkLite) +S3method(get.neighborhood,networkLite) S3method(get.network.attribute,networkLite) S3method(get.vertex.attribute,networkLite) +S3method(has.edges,networkLite) +S3method(is.adjacent,networkLite) S3method(is.na,networkLite) S3method(list.edge.attributes,networkLite) S3method(list.network.attributes,networkLite) S3method(list.vertex.attributes,networkLite) S3method(mixingmatrix,networkLite) +S3method(network.density,networkLite) S3method(network.edgecount,networkLite) S3method(network.naedgecount,networkLite) S3method(networkLite,edgelist) diff --git a/R/network_methods.R b/R/network_methods.R new file mode 100644 index 0000000..924b06f --- /dev/null +++ b/R/network_methods.R @@ -0,0 +1,318 @@ + +#' @rdname get.edgeIDs +#' +#' @title Get Edge IDs for Specified Dyads +#' +#' @param x A `networkLite` object. +#' @param v Vertex ID. +#' @param alter Vertex ID for the alter (optional). If NULL, returns incident edges. +#' @param neighborhood Specifies which edges to return when alter is NULL. +#' @param na.omit Logical; whether to exclude missing edges from the result. +#' @param ... additional arguments. +#' +#' @return The edge ID (row index in `x$el`) for the specified dyad, or +#' numeric(0) if the edge is not present. For directed networks, the edge +#' from `v` to `alter` is returned. For undirected networks, the edge +#' between `v` and `alter` is returned (order does not matter). +#' +#' @details +#' Returns the edge ID for a single dyad. In networkLite, edge IDs are +#' simply row indices within `x$el`. If the edge is not present, returns +#' numeric(0). If `na.omit = TRUE`, missing edges (those with the "na" +#' attribute set to TRUE) are excluded. +#' +#' @export +#' +get.edgeIDs.networkLite <- function(x, v, alter = NULL, + neighborhood = c("out", "in", "combined"), + na.omit = TRUE, ...) { + neighborhood <- match.arg(neighborhood) + + if (is.null(alter)) { + # Return incident edges when alter is NULL + return(get.edges(x, v, neighborhood = neighborhood, na.omit = na.omit)) + } + + v <- as.integer(v) + alter <- as.integer(alter) + + if (length(v) != 1 || length(alter) != 1) { + stop("get.edgeIDs requires scalar v and alter; use get.dyads.eids for vectors") + } + + if (is.na(v) || is.na(alter) || v < 1 || v > network.size(x) || + alter < 1 || alter > network.size(x)) { + return(numeric(0)) + } + + # For undirected networks, normalize the dyad so tail < head + if (!is.directed(x) && v > alter) { + temp <- v + v <- alter + alter <- temp + } + + # Find the edge in the edgelist + eid <- which(x$el$.tail == v & x$el$.head == alter) + + # If na.omit is TRUE, exclude edges with na = TRUE + if (na.omit && length(eid) > 0 && isTRUE(x$el$na[eid])) { + return(numeric(0)) + } + + return(eid) +} + + +#' @rdname get.dyads.eids +#' +#' @title Get Edge IDs for Multiple Dyads +#' +#' @param x A `networkLite` object. +#' @param tails Vector of tail vertex IDs. +#' @param heads Vector of head vertex IDs (must be same length as tails). +#' @param neighborhood Specifies which edges to consider. +#' @param na.omit Logical; whether to exclude missing edges from the result. +#' @param ... additional arguments. +#' +#' @return A list of edge IDs corresponding to the specified dyads. Each +#' element is either a single edge ID or numeric(0) if the edge is not +#' present. +#' +#' @details +#' Vectorized version of `get.edgeIDs`. Returns a list where each element +#' corresponds to the edge ID for the dyad (tails[i], heads[i]). +#' +#' @export +#' +get.dyads.eids.networkLite <- function(x, tails, heads, + neighborhood = c("out", "in", "combined"), + na.omit = TRUE, ...) { + neighborhood <- match.arg(neighborhood) + tails <- as.integer(tails) + heads <- as.integer(heads) + + if (length(tails) != length(heads)) { + stop("tails and heads must have the same length") + } + + # Use lapply to get edge IDs for each dyad + eids <- lapply(seq_along(tails), function(i) { + get.edgeIDs(x, tails[i], heads[i], neighborhood = neighborhood, na.omit = na.omit) + }) + + return(eids) +} + + +#' @rdname get.edges +#' +#' @title Get Edges +#' +#' @param x A `networkLite` object. +#' @param v,alter Vertex IDs. If both are provided, returns edges between +#' v and alter. If only v is provided, returns edges incident on v. +#' @param neighborhood Specifies which edges to return: "out" for outgoing +#' edges, "in" for incoming edges, "combined" for both. +#' @param na.omit Logical; whether to exclude missing edges from the result. +#' @param ... additional arguments. +#' +#' @return A vector of edge IDs. +#' +#' @details +#' Returns edge IDs based on the vertex selection. If both `v` and `alter` +#' are specified, returns edges between those vertices. If only `v` is +#' specified, returns edges incident on `v` according to the `neighborhood` +#' parameter. +#' +#' @export +#' +get.edges.networkLite <- function(x, v, alter, neighborhood = c("combined", "out", "in"), + na.omit = TRUE, ...) { + neighborhood <- match.arg(neighborhood) + + if (!missing(alter)) { + # Get edges between v and alter + return(unlist(get.dyads.eids(x, v, alter, na.omit = na.omit))) + } + + v <- as.integer(v) + + # Get edges incident on v + if (neighborhood == "out" || neighborhood == "combined") { + out_edges <- which(x$el$.tail %in% v) + } else { + out_edges <- integer(0) + } + + if (neighborhood == "in" || (neighborhood == "combined" && is.directed(x))) { + in_edges <- which(x$el$.head %in% v) + } else if (neighborhood == "combined" && !is.directed(x)) { + # For undirected networks, also check heads + in_edges <- which(x$el$.head %in% v) + } else { + in_edges <- integer(0) + } + + eids <- unique(c(out_edges, in_edges)) + + # Filter out missing edges if na.omit is TRUE + if (na.omit && length(eids) > 0) { + eids <- eids[!NVL(x$el$na[eids], FALSE)] + } + + return(eids) +} + + +#' @rdname get.neighborhood +#' +#' @title Get Neighborhood of Vertices +#' +#' @param x A `networkLite` object. +#' @param v Vertex ID or vector of vertex IDs. +#' @param type Specifies which neighbors to return: "out" for out-neighbors, +#' "in" for in-neighbors, "combined" for both. +#' @param na.omit Logical; whether to exclude neighbors connected by missing edges. +#' @param ... additional arguments. +#' +#' @return A vector of vertex IDs representing the neighborhood of v. +#' +#' @details +#' Returns the neighborhood (adjacent vertices) of the specified vertex or +#' vertices. For directed networks, the type parameter controls whether +#' out-neighbors, in-neighbors, or both are returned. +#' +#' @export +#' +get.neighborhood.networkLite <- function(x, v, type = c("combined", "out", "in"), + na.omit = TRUE, ...) { + type <- match.arg(type) + v <- as.integer(v) + + neighbors <- integer(0) + + # Get out-neighbors (vertices that v points to) + if (type == "out" || type == "combined") { + out_idx <- which(x$el$.tail %in% v) + if (na.omit) { + out_idx <- out_idx[!NVL(x$el$na[out_idx], FALSE)] + } + neighbors <- c(neighbors, x$el$.head[out_idx]) + } + + # Get in-neighbors (vertices that point to v) + if (type == "in" || type == "combined") { + in_idx <- which(x$el$.head %in% v) + if (na.omit) { + in_idx <- in_idx[!NVL(x$el$na[in_idx], FALSE)] + } + neighbors <- c(neighbors, x$el$.tail[in_idx]) + } + + # Return unique neighbors, excluding v itself + unique(setdiff(neighbors, v)) +} + + +#' @rdname is.adjacent +#' +#' @title Test for Edge Existence +#' +#' @param x A `networkLite` object. +#' @param vi,vj Vertex IDs. +#' @param na.omit Logical; whether to treat missing edges as non-existent. +#' @param ... additional arguments. +#' +#' @return Logical indicating whether an edge exists from vi to vj (or +#' between vi and vj for undirected networks). +#' +#' @details +#' Tests whether an edge exists between the specified vertices. For directed +#' networks, tests for an edge from vi to vj. For undirected networks, tests +#' for an edge between vi and vj (order does not matter). +#' +#' @export +#' +is.adjacent.networkLite <- function(x, vi, vj, na.omit = FALSE, ...) { + eid <- get.edgeIDs(x, vi, vj, na.omit = na.omit) + length(eid) > 0 +} + + +#' @rdname network.density +#' +#' @title Calculate Network Density +#' +#' @param x A `networkLite` object. +#' @param na.omit Logical; whether to exclude missing edges from the calculation. +#' @param discount.bipartite Logical; for bipartite networks, whether to compute +#' density based on within-mode edges (if FALSE) or only between-mode edges (if TRUE). +#' @param ... additional arguments. +#' +#' @return The network density (proportion of possible edges that are present). +#' +#' @details +#' Calculates the density of the network as the ratio of the number of edges +#' to the number of possible edges. For directed networks, the number of +#' possible edges is n*(n-1). For undirected networks, it is n*(n-1)/2, +#' where n is the network size. For bipartite networks, the number of +#' possible edges is n1*n2 when discount.bipartite = FALSE, where n1 and n2 +#' are the sizes of the two modes. +#' +#' @export +#' +network.density.networkLite <- function(x, na.omit = TRUE, discount.bipartite = FALSE, ...) { + n <- network.size(x) + + if (n == 0) { + return(NaN) + } + + edge_count <- network.edgecount(x, na.omit = na.omit) + + if (is.bipartite(x) && !discount.bipartite) { + b1 <- x %n% "bipartite" + b2 <- n - b1 + max_edges <- b1 * b2 + } else if (is.directed(x)) { + max_edges <- n * (n - 1) + } else { + max_edges <- n * (n - 1) / 2 + } + + if (max_edges == 0) { + return(NaN) + } + + edge_count / max_edges +} + + +#' @rdname has.edges +#' +#' @title Test for Edge Existence in Network +#' +#' @param net A `networkLite` object. +#' @param v Vertex IDs to check for incident edges. Defaults to all vertices. +#' @param ... additional arguments. +#' +#' @return Logical indicating whether the specified vertices have any incident edges. +#' +#' @details +#' Returns TRUE if any of the specified vertices have at least one incident edge +#' (excluding missing edges), FALSE otherwise. +#' +#' @export +#' +has.edges.networkLite <- function(net, v = seq_len(network.size(net)), ...) { + if (length(v) == 0 || network.edgecount(net, na.omit = TRUE) == 0) { + return(FALSE) + } + + v <- as.integer(v) + + # Check if any edges involve the specified vertices + any(net$el$.tail %in% v | net$el$.head %in% v) && + any(!NVL(net$el$na[net$el$.tail %in% v | net$el$.head %in% v], FALSE)) +} diff --git a/tests/testthat/test-network-methods.R b/tests/testthat/test-network-methods.R new file mode 100644 index 0000000..d64cd87 --- /dev/null +++ b/tests/testthat/test-network-methods.R @@ -0,0 +1,201 @@ +library(testthat) +library(network) + +test_that("get.edgeIDs works for networkLite", { + # Create a simple directed network + nw <- networkLite(5, directed = TRUE) + add.edges(nw, c(1, 2, 3), c(2, 3, 4)) + + # Test getting edge IDs + eid1 <- get.edgeIDs(nw, 1, 2) + expect_equal(length(eid1), 1) + expect_true(eid1 > 0) + + eid2 <- get.edgeIDs(nw, 2, 3) + expect_equal(length(eid2), 1) + expect_true(eid2 > 0) + + # Test non-existent edge + eid_none <- get.edgeIDs(nw, 1, 5) + expect_equal(length(eid_none), 0) + + # Test undirected network + nw_undir <- networkLite(5, directed = FALSE) + add.edges(nw_undir, c(1, 2), c(2, 3)) + + # For undirected, order shouldn't matter + eid_a <- get.edgeIDs(nw_undir, 1, 2) + eid_b <- get.edgeIDs(nw_undir, 2, 1) + expect_equal(eid_a, eid_b) +}) + +test_that("get.edgeIDs handles missing edges", { + nw <- networkLite(5, directed = TRUE) + add.edges(nw, c(1, 2), c(2, 3), names.eval = list("na", "na"), + vals.eval = list(TRUE, FALSE)) + + # With na.omit = TRUE, missing edge should not be returned + eid1 <- get.edgeIDs(nw, 1, 2, na.omit = TRUE) + expect_equal(length(eid1), 0) + + # With na.omit = FALSE, missing edge should be returned + eid2 <- get.edgeIDs(nw, 1, 2, na.omit = FALSE) + expect_equal(length(eid2), 1) + + # Non-missing edge should be returned either way + eid3 <- get.edgeIDs(nw, 2, 3, na.omit = TRUE) + expect_equal(length(eid3), 1) + eid4 <- get.edgeIDs(nw, 2, 3, na.omit = FALSE) + expect_equal(length(eid4), 1) +}) + +test_that("get.dyads.eids works for networkLite", { + nw <- networkLite(5, directed = TRUE) + add.edges(nw, c(1, 2, 3), c(2, 3, 4)) + + # Test getting multiple edge IDs + eids <- get.dyads.eids(nw, c(1, 2, 3), c(2, 3, 4)) + expect_equal(length(eids), 3) + expect_true(all(sapply(eids, length) == 1)) + + # Test with some non-existent edges + eids2 <- get.dyads.eids(nw, c(1, 1, 2), c(2, 5, 3)) + expect_equal(length(eids2), 3) + expect_equal(length(eids2[[1]]), 1) # exists + expect_equal(length(eids2[[2]]), 0) # doesn't exist + expect_equal(length(eids2[[3]]), 1) # exists +}) + +test_that("get.edges works for networkLite", { + nw <- networkLite(5, directed = TRUE) + add.edges(nw, c(1, 2, 3, 1), c(2, 3, 4, 3)) + + # Test getting edges incident on a vertex + edges1 <- get.edges(nw, v = 1) + expect_true(length(edges1) > 0) + + # Test getting outgoing edges + edges_out <- get.edges(nw, v = 1, neighborhood = "out") + expect_true(length(edges_out) > 0) + + # Test getting incoming edges + edges_in <- get.edges(nw, v = 3, neighborhood = "in") + expect_true(length(edges_in) > 0) + + # Test getting edge between two specific vertices + edges_between <- get.edges(nw, v = 1, alter = 2) + expect_equal(length(edges_between), 1) +}) + +test_that("get.neighborhood works for networkLite", { + nw <- networkLite(5, directed = TRUE) + add.edges(nw, c(1, 2, 3), c(2, 3, 4)) + + # Test getting neighbors + neighbors1 <- get.neighborhood(nw, 1) + expect_true(2 %in% neighbors1) + + # Test out-neighbors + neighbors_out <- get.neighborhood(nw, 2, type = "out") + expect_true(3 %in% neighbors_out) + + # Test in-neighbors + neighbors_in <- get.neighborhood(nw, 3, type = "in") + expect_true(2 %in% neighbors_in) + + # Test undirected network + nw_undir <- networkLite(5, directed = FALSE) + add.edges(nw_undir, c(1, 2), c(2, 3)) + neighbors_undir <- get.neighborhood(nw_undir, 2) + expect_true(1 %in% neighbors_undir) + expect_true(3 %in% neighbors_undir) +}) + +test_that("is.adjacent works for networkLite", { + nw <- networkLite(5, directed = TRUE) + add.edges(nw, c(1, 2, 3), c(2, 3, 4)) + + # Test existing edge + expect_true(is.adjacent(nw, 1, 2)) + expect_true(is.adjacent(nw, 2, 3)) + + # Test non-existent edge + expect_false(is.adjacent(nw, 1, 5)) + expect_false(is.adjacent(nw, 5, 1)) + + # Test directed network - order matters + expect_true(is.adjacent(nw, 1, 2)) + expect_false(is.adjacent(nw, 2, 1)) + + # Test undirected network - order doesn't matter + nw_undir <- networkLite(5, directed = FALSE) + add.edges(nw_undir, c(1, 2), c(2, 3)) + expect_true(is.adjacent(nw_undir, 1, 2)) + expect_true(is.adjacent(nw_undir, 2, 1)) +}) + +test_that("network.density works for networkLite", { + # Test directed network + nw_dir <- networkLite(5, directed = TRUE) + expect_equal(network.density(nw_dir), 0) + + add.edges(nw_dir, c(1, 2), c(2, 3)) + # 2 edges out of 5*4 = 20 possible + expect_equal(network.density(nw_dir), 2 / 20) + + # Test undirected network + nw_undir <- networkLite(5, directed = FALSE) + add.edges(nw_undir, c(1, 2), c(2, 3)) + # 2 edges out of 5*4/2 = 10 possible + expect_equal(network.density(nw_undir), 2 / 10) + + # Test bipartite network + nw_bip <- networkLite(6, directed = FALSE, bipartite = 3) + add.edges(nw_bip, c(1, 2), c(4, 5)) + # 2 edges out of 3*3 = 9 possible + expect_equal(network.density(nw_bip), 2 / 9) + + # Test empty network + nw_empty <- networkLite(0) + expect_true(is.nan(network.density(nw_empty))) +}) + +test_that("has.edges works for networkLite", { + nw <- networkLite(5, directed = TRUE) + + # Empty network + expect_false(has.edges(nw)) + + # Network with edges + add.edges(nw, c(1, 2), c(2, 3)) + expect_true(has.edges(nw)) + + # Network with only missing edges + nw2 <- networkLite(5, directed = TRUE) + add.edges(nw2, c(1), c(2), names.eval = list("na"), vals.eval = list(TRUE)) + expect_false(has.edges(nw2)) +}) + +test_that("methods work together with network package", { + # Create a network using the network package + nw_net <- network.initialize(5, directed = TRUE) + add.edges(nw_net, c(1, 2, 3), c(2, 3, 4)) + + # Convert to networkLite + nw_lite <- as.networkLite(nw_net) + + # Test that edge IDs work consistently + eid_net <- get.edgeIDs(nw_net, 1, 2) + eid_lite <- get.edgeIDs(nw_lite, 1, 2) + expect_equal(length(eid_net), length(eid_lite)) + + # Test density + expect_equal(network.density(nw_net), network.density(nw_lite)) + + # Test adjacency + expect_equal(is.adjacent(nw_net, 1, 2), is.adjacent(nw_lite, 1, 2)) + expect_equal(is.adjacent(nw_net, 2, 1), is.adjacent(nw_lite, 2, 1)) + + # Test has.edges + expect_equal(has.edges(nw_net), has.edges(nw_lite)) +}) From 48f0a0755b36e813f939cb3c334755da334ab385 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:27:57 +0000 Subject: [PATCH 3/5] Fix network methods to work as S3 generics Co-authored-by: krivit <15682462+krivit@users.noreply.github.com> --- NAMESPACE | 12 +++++ R/network_methods.R | 106 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 12abb4a..d62951f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,16 +24,22 @@ S3method(delete.edges,networkLite) S3method(delete.network.attribute,networkLite) S3method(delete.vertex.attribute,networkLite) S3method(delete.vertices,networkLite) +S3method(get.dyads.eids,default) S3method(get.dyads.eids,networkLite) S3method(get.edge.attribute,networkLite) S3method(get.edge.value,networkLite) +S3method(get.edgeIDs,default) S3method(get.edgeIDs,networkLite) +S3method(get.edges,default) S3method(get.edges,networkLite) S3method(get.inducedSubgraph,networkLite) +S3method(get.neighborhood,default) S3method(get.neighborhood,networkLite) S3method(get.network.attribute,networkLite) S3method(get.vertex.attribute,networkLite) +S3method(has.edges,default) S3method(has.edges,networkLite) +S3method(is.adjacent,default) S3method(is.adjacent,networkLite) S3method(is.na,networkLite) S3method(list.edge.attributes,networkLite) @@ -55,6 +61,12 @@ S3method(set.vertex.attribute,networkLite) S3method(valid.eids,networkLite) export(as.networkLite) export(atomize) +export(get.dyads.eids) +export(get.edgeIDs) +export(get.edges) +export(get.neighborhood) +export(has.edges) +export(is.adjacent) export(is.networkLite) export(networkLite) export(networkLite_initialize) diff --git a/R/network_methods.R b/R/network_methods.R index 924b06f..4abc77e 100644 --- a/R/network_methods.R +++ b/R/network_methods.R @@ -1,4 +1,78 @@ +# Re-export network functions as S3 generics for networkLite + +#' @name get.edgeIDs +#' @export +get.edgeIDs <- function(x, ...) { + UseMethod("get.edgeIDs") +} + +#' @export +get.edgeIDs.default <- function(x, v, alter = NULL, + neighborhood = c("out", "in", "combined"), + na.omit = TRUE, ...) { + network::get.edgeIDs(x, v, alter, neighborhood, na.omit, ...) +} + +#' @name get.dyads.eids +#' @export +get.dyads.eids <- function(x, ...) { + UseMethod("get.dyads.eids") +} + +#' @export +get.dyads.eids.default <- function(x, tails, heads, + neighborhood = c("out", "in", "combined"), + na.omit = TRUE, ...) { + network::get.dyads.eids(x, tails, heads, neighborhood, na.omit, ...) +} + +#' @name get.edges +#' @export +get.edges <- function(x, ...) { + UseMethod("get.edges") +} + +#' @export +get.edges.default <- function(x, v, alter, neighborhood = c("combined", "out", "in"), + na.omit = TRUE, ...) { + network::get.edges(x, v, alter, neighborhood, na.omit, ...) +} + +#' @name get.neighborhood +#' @export +get.neighborhood <- function(x, ...) { + UseMethod("get.neighborhood") +} + +#' @export +get.neighborhood.default <- function(x, v, type = c("combined", "out", "in"), + na.omit = TRUE, ...) { + network::get.neighborhood(x, v, type, na.omit, ...) +} + +#' @name is.adjacent +#' @export +is.adjacent <- function(x, ...) { + UseMethod("is.adjacent") +} + +#' @export +is.adjacent.default <- function(x, vi, vj, na.omit = FALSE, ...) { + network::is.adjacent(x, vi, vj, na.omit, ...) +} + +#' @name has.edges +#' @export +has.edges <- function(net, ...) { + UseMethod("has.edges") +} + +#' @export +has.edges.default <- function(net, v = seq_len(network.size(net)), ...) { + network::has.edges(net, v, ...) +} + #' @rdname get.edgeIDs #' #' @title Get Edge IDs for Specified Dyads @@ -28,12 +102,40 @@ get.edgeIDs.networkLite <- function(x, v, alter = NULL, na.omit = TRUE, ...) { neighborhood <- match.arg(neighborhood) + v <- as.integer(v) + if (is.null(alter)) { # Return incident edges when alter is NULL - return(get.edges(x, v, neighborhood = neighborhood, na.omit = na.omit)) + if (length(v) != 1) { + stop("get.edgeIDs requires scalar v") + } + + # Get edges incident on v + if (neighborhood == "out" || neighborhood == "combined") { + out_edges <- which(x$el$.tail %in% v) + } else { + out_edges <- integer(0) + } + + if (neighborhood == "in" || (neighborhood == "combined" && is.directed(x))) { + in_edges <- which(x$el$.head %in% v) + } else if (neighborhood == "combined" && !is.directed(x)) { + # For undirected networks, also check heads + in_edges <- which(x$el$.head %in% v) + } else { + in_edges <- integer(0) + } + + eids <- unique(c(out_edges, in_edges)) + + # Filter out missing edges if na.omit is TRUE + if (na.omit && length(eids) > 0) { + eids <- eids[!NVL(x$el$na[eids], FALSE)] + } + + return(eids) } - v <- as.integer(v) alter <- as.integer(alter) if (length(v) != 1 || length(alter) != 1) { From 7a9144c92440f015d092a35462c00a82052928da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:31:15 +0000 Subject: [PATCH 4/5] Address code review feedback: fix na.omit handling and remove network.density Co-authored-by: krivit <15682462+krivit@users.noreply.github.com> --- NAMESPACE | 1 - R/network_methods.R | 91 ++++++++++++++++----------------------------- 2 files changed, 33 insertions(+), 59 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index d62951f..93de418 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -46,7 +46,6 @@ S3method(list.edge.attributes,networkLite) S3method(list.network.attributes,networkLite) S3method(list.vertex.attributes,networkLite) S3method(mixingmatrix,networkLite) -S3method(network.density,networkLite) S3method(network.edgecount,networkLite) S3method(network.naedgecount,networkLite) S3method(networkLite,edgelist) diff --git a/R/network_methods.R b/R/network_methods.R index 4abc77e..54c117a 100644 --- a/R/network_methods.R +++ b/R/network_methods.R @@ -130,7 +130,9 @@ get.edgeIDs.networkLite <- function(x, v, alter = NULL, # Filter out missing edges if na.omit is TRUE if (na.omit && length(eids) > 0) { - eids <- eids[!NVL(x$el$na[eids], FALSE)] + # Check each edge's na attribute + na_vals <- sapply(eids, function(i) isTRUE(x$el$na[[i]])) + eids <- eids[!na_vals] } return(eids) @@ -158,8 +160,11 @@ get.edgeIDs.networkLite <- function(x, v, alter = NULL, eid <- which(x$el$.tail == v & x$el$.head == alter) # If na.omit is TRUE, exclude edges with na = TRUE - if (na.omit && length(eid) > 0 && isTRUE(x$el$na[eid])) { - return(numeric(0)) + if (na.omit && length(eid) > 0) { + na_val <- x$el$na[[eid]] # Use [[]] to get the scalar value from list + if (isTRUE(na_val)) { + return(numeric(0)) + } } return(eid) @@ -260,7 +265,9 @@ get.edges.networkLite <- function(x, v, alter, neighborhood = c("combined", "out # Filter out missing edges if na.omit is TRUE if (na.omit && length(eids) > 0) { - eids <- eids[!NVL(x$el$na[eids], FALSE)] + # Check each edge's na attribute + na_vals <- sapply(eids, function(i) isTRUE(x$el$na[[i]])) + eids <- eids[!na_vals] } return(eids) @@ -298,7 +305,9 @@ get.neighborhood.networkLite <- function(x, v, type = c("combined", "out", "in") if (type == "out" || type == "combined") { out_idx <- which(x$el$.tail %in% v) if (na.omit) { - out_idx <- out_idx[!NVL(x$el$na[out_idx], FALSE)] + # Filter out missing edges + na_vals <- sapply(out_idx, function(i) isTRUE(x$el$na[[i]])) + out_idx <- out_idx[!na_vals] } neighbors <- c(neighbors, x$el$.head[out_idx]) } @@ -307,7 +316,9 @@ get.neighborhood.networkLite <- function(x, v, type = c("combined", "out", "in") if (type == "in" || type == "combined") { in_idx <- which(x$el$.head %in% v) if (na.omit) { - in_idx <- in_idx[!NVL(x$el$na[in_idx], FALSE)] + # Filter out missing edges + na_vals <- sapply(in_idx, function(i) isTRUE(x$el$na[[i]])) + in_idx <- in_idx[!na_vals] } neighbors <- c(neighbors, x$el$.tail[in_idx]) } @@ -324,6 +335,7 @@ get.neighborhood.networkLite <- function(x, v, type = c("combined", "out", "in") #' @param x A `networkLite` object. #' @param vi,vj Vertex IDs. #' @param na.omit Logical; whether to treat missing edges as non-existent. +#' Default is FALSE to match network package behavior. #' @param ... additional arguments. #' #' @return Logical indicating whether an edge exists from vi to vj (or @@ -332,65 +344,20 @@ get.neighborhood.networkLite <- function(x, v, type = c("combined", "out", "in") #' @details #' Tests whether an edge exists between the specified vertices. For directed #' networks, tests for an edge from vi to vj. For undirected networks, tests -#' for an edge between vi and vj (order does not matter). +#' for an edge between vi and vj (order does not matter). Note that the +#' default for na.omit is FALSE, meaning missing edges are treated as +#' present by default (consistent with network package behavior). #' #' @export #' is.adjacent.networkLite <- function(x, vi, vj, na.omit = FALSE, ...) { + # Explicitly pass na.omit to get.edgeIDs + # Note: get.edgeIDs has default na.omit=TRUE, but we override it here eid <- get.edgeIDs(x, vi, vj, na.omit = na.omit) length(eid) > 0 } -#' @rdname network.density -#' -#' @title Calculate Network Density -#' -#' @param x A `networkLite` object. -#' @param na.omit Logical; whether to exclude missing edges from the calculation. -#' @param discount.bipartite Logical; for bipartite networks, whether to compute -#' density based on within-mode edges (if FALSE) or only between-mode edges (if TRUE). -#' @param ... additional arguments. -#' -#' @return The network density (proportion of possible edges that are present). -#' -#' @details -#' Calculates the density of the network as the ratio of the number of edges -#' to the number of possible edges. For directed networks, the number of -#' possible edges is n*(n-1). For undirected networks, it is n*(n-1)/2, -#' where n is the network size. For bipartite networks, the number of -#' possible edges is n1*n2 when discount.bipartite = FALSE, where n1 and n2 -#' are the sizes of the two modes. -#' -#' @export -#' -network.density.networkLite <- function(x, na.omit = TRUE, discount.bipartite = FALSE, ...) { - n <- network.size(x) - - if (n == 0) { - return(NaN) - } - - edge_count <- network.edgecount(x, na.omit = na.omit) - - if (is.bipartite(x) && !discount.bipartite) { - b1 <- x %n% "bipartite" - b2 <- n - b1 - max_edges <- b1 * b2 - } else if (is.directed(x)) { - max_edges <- n * (n - 1) - } else { - max_edges <- n * (n - 1) / 2 - } - - if (max_edges == 0) { - return(NaN) - } - - edge_count / max_edges -} - - #' @rdname has.edges #' #' @title Test for Edge Existence in Network @@ -415,6 +382,14 @@ has.edges.networkLite <- function(net, v = seq_len(network.size(net)), ...) { v <- as.integer(v) # Check if any edges involve the specified vertices - any(net$el$.tail %in% v | net$el$.head %in% v) && - any(!NVL(net$el$na[net$el$.tail %in% v | net$el$.head %in% v], FALSE)) + if (length(v) > 0 && network.edgecount(net, na.omit = TRUE) > 0) { + edge_indices <- which(net$el$.tail %in% v | net$el$.head %in% v) + if (length(edge_indices) > 0) { + # Check if any of these edges are not missing + na_vals <- sapply(edge_indices, function(i) isTRUE(net$el$na[[i]])) + return(any(!na_vals)) + } + } + + return(FALSE) } From 0373063c9e414b15c7e18821544ec2a8aa6b9e13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 06:17:09 +0000 Subject: [PATCH 5/5] Remove generic definitions - use network package generics instead Co-authored-by: krivit <15682462+krivit@users.noreply.github.com> --- NAMESPACE | 12 ------- R/network_methods.R | 76 ++------------------------------------------- 2 files changed, 2 insertions(+), 86 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 93de418..0e2701f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,22 +24,16 @@ S3method(delete.edges,networkLite) S3method(delete.network.attribute,networkLite) S3method(delete.vertex.attribute,networkLite) S3method(delete.vertices,networkLite) -S3method(get.dyads.eids,default) S3method(get.dyads.eids,networkLite) S3method(get.edge.attribute,networkLite) S3method(get.edge.value,networkLite) -S3method(get.edgeIDs,default) S3method(get.edgeIDs,networkLite) -S3method(get.edges,default) S3method(get.edges,networkLite) S3method(get.inducedSubgraph,networkLite) -S3method(get.neighborhood,default) S3method(get.neighborhood,networkLite) S3method(get.network.attribute,networkLite) S3method(get.vertex.attribute,networkLite) -S3method(has.edges,default) S3method(has.edges,networkLite) -S3method(is.adjacent,default) S3method(is.adjacent,networkLite) S3method(is.na,networkLite) S3method(list.edge.attributes,networkLite) @@ -60,12 +54,6 @@ S3method(set.vertex.attribute,networkLite) S3method(valid.eids,networkLite) export(as.networkLite) export(atomize) -export(get.dyads.eids) -export(get.edgeIDs) -export(get.edges) -export(get.neighborhood) -export(has.edges) -export(is.adjacent) export(is.networkLite) export(networkLite) export(networkLite_initialize) diff --git a/R/network_methods.R b/R/network_methods.R index 54c117a..bacd05f 100644 --- a/R/network_methods.R +++ b/R/network_methods.R @@ -1,77 +1,5 @@ - -# Re-export network functions as S3 generics for networkLite - -#' @name get.edgeIDs -#' @export -get.edgeIDs <- function(x, ...) { - UseMethod("get.edgeIDs") -} - -#' @export -get.edgeIDs.default <- function(x, v, alter = NULL, - neighborhood = c("out", "in", "combined"), - na.omit = TRUE, ...) { - network::get.edgeIDs(x, v, alter, neighborhood, na.omit, ...) -} - -#' @name get.dyads.eids -#' @export -get.dyads.eids <- function(x, ...) { - UseMethod("get.dyads.eids") -} - -#' @export -get.dyads.eids.default <- function(x, tails, heads, - neighborhood = c("out", "in", "combined"), - na.omit = TRUE, ...) { - network::get.dyads.eids(x, tails, heads, neighborhood, na.omit, ...) -} - -#' @name get.edges -#' @export -get.edges <- function(x, ...) { - UseMethod("get.edges") -} - -#' @export -get.edges.default <- function(x, v, alter, neighborhood = c("combined", "out", "in"), - na.omit = TRUE, ...) { - network::get.edges(x, v, alter, neighborhood, na.omit, ...) -} - -#' @name get.neighborhood -#' @export -get.neighborhood <- function(x, ...) { - UseMethod("get.neighborhood") -} - -#' @export -get.neighborhood.default <- function(x, v, type = c("combined", "out", "in"), - na.omit = TRUE, ...) { - network::get.neighborhood(x, v, type, na.omit, ...) -} - -#' @name is.adjacent -#' @export -is.adjacent <- function(x, ...) { - UseMethod("is.adjacent") -} - -#' @export -is.adjacent.default <- function(x, vi, vj, na.omit = FALSE, ...) { - network::is.adjacent(x, vi, vj, na.omit, ...) -} - -#' @name has.edges -#' @export -has.edges <- function(net, ...) { - UseMethod("has.edges") -} - -#' @export -has.edges.default <- function(net, v = seq_len(network.size(net)), ...) { - network::has.edges(net, v, ...) -} +# Network method implementations for networkLite +# These methods implement the S3 generics defined in the network package #' @rdname get.edgeIDs #'