From f29f953eced007c1d6fb8f1b22b8db25886b81eb Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 26 Aug 2026 15:51:28 +0200 Subject: [PATCH 1/2] This version fixes three failures in the most recent cran submission that were bugs in `manynet` --- DESCRIPTION | 2 +- NEWS.md | 16 +++++++++++ R/mark_changes.R | 6 ++++- R/measure_attributes.R | 18 +++++++++++++ R/reexports_classes.R | 46 ++++++++++++++++++-------------- cran-comments.md | 17 ++++++++++++ tests/testthat/test-manip_grab.R | 33 +++++++++++++++++++++++ tests/testthat/test-mark_is.R | 14 ++++++++++ 8 files changed, 130 insertions(+), 22 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e0bdc058..f801baa0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: manynet Title: Many Ways to Make, Manipulate, and Modify Myriad Networks -Version: 2.3.0 +Version: 2.3.1 Description: Many tools for making, manipulating, and modifying many different types of networks. All functions operate with matrices, edge lists, and 'igraph', 'network', and 'tidygraph' objects, on directed, multiplex, multimodal, signed, and other networks. diff --git a/NEWS.md b/NEWS.md index cd56582f..57a0e1b9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,19 @@ +# manynet 2.3.1 + +## Marking + +- Fixed `is_longitudinal()` marking a network whose ties carry no moments + +## Measures + +- Fixed `tie_attribute.stocnet)` and `node_attribute.stocnet())` + - Returns every attribute where no attribute is named, as `.igraph` method already did + - Reports tie attributes without 'from' and 'to', which identify a tie rather than describe it + +## Manipulating + +- Fixed a mark inside `filter_ties()` or `mutate_ties()` reading correct `{tidygraph}` context + # manynet 2.3.0 ## Package diff --git a/R/mark_changes.R b/R/mark_changes.R index df436e9c..dee3ff64 100644 --- a/R/mark_changes.R +++ b/R/mark_changes.R @@ -33,7 +33,11 @@ is_longitudinal.default <- function(.data) { is_longitudinal.igraph <- function(.data) { # A panel network re-observes the whole network at each moment, so each # moment replaces the one before it. See `.time_rule()`. - identical(.time_rule(.data), "replace") && !is.null(.time_moments(.data)) + # It re-observes the ties too, so the ties carry the stamp. A network that + # records only nodal changes, such as a diffusion on a static network, + # is therefore not a panel. + identical(.time_rule(.data), "replace") && !is.null(.time_moments(.data)) && + any(c("time", "wave", "panel") %in% net_tie_attributes(.data)) } #' @export diff --git a/R/measure_attributes.R b/R/measure_attributes.R index d2baff17..5657b670 100644 --- a/R/measure_attributes.R +++ b/R/measure_attributes.R @@ -31,12 +31,17 @@ node_attribute.default <- function(.data, attr_name){ #' @export node_attribute.stocnet <- function(.data, attr_name){ + # `igraph::vertex_attr()` returns every attribute where none is named, + # so every other class returns every attribute there too. + if(missing(attr_name)) return(as.list(.data$nodes)) out <- .data$nodes[[attr_name]] if(is.numeric(out)) make_node_measure(out, .data) else out } #' @export node_attribute.network <- function(.data, attr_name){ + if(missing(attr_name)) return(.all_attributes(.data, node_attribute, + net_node_attributes(.data))) network::get.vertex.attribute(.data, attr_name) } @@ -115,15 +120,28 @@ tie_attribute.default <- function(.data, attr_name){ #' @export tie_attribute.stocnet <- function(.data, attr_name){ + # `igraph::edge_attr()` returns every attribute where none is named, + # so every other class returns every attribute there too. + # 'from' and 'to' identify a tie rather than describe it, so they are dropped. + if(missing(attr_name)) + return(as.list(.data$ties[setdiff(names(.data$ties), c("from", "to"))])) out <- .data$ties[[attr_name]] if(is.numeric(out)) make_tie_measure(out, .data) else out } #' @export tie_attribute.network <- function(.data, attr_name){ + if(missing(attr_name)) return(.all_attributes(.data, tie_attribute, + net_tie_attributes(.data))) network::get.edge.attribute(.data, attr_name) } +# Collects every named attribute into a list, as `igraph::vertex_attr()` and +# `igraph::edge_attr()` do where the caller names no attribute. +.all_attributes <- function(.data, FUN, attr_names){ + stats::setNames(lapply(attr_names, function(x) FUN(.data, x)), attr_names) +} + #' @rdname measure_attributes_ties #' @examples #' tie_weights(to_mode1(ison_southern_women)) diff --git a/R/reexports_classes.R b/R/reexports_classes.R index e751d371..33e3be0f 100644 --- a/R/reexports_classes.R +++ b/R/reexports_classes.R @@ -102,25 +102,10 @@ clear_active_context <- function() { active_network <- function(required = NULL) { - ctx <- get_active_context() - - if (!is.null(ctx$data)) { - - if (!is.null(required) && - !identical(ctx$active, required)) { - snet_abort( - paste0( - "This call requires ", - if (identical(required, "edges")) "ties" else required, - " to be active" - ), - call. = FALSE - ) - } - - return(ctx$data) - } - + # A tidygraph context is set only inside a tidygraph verb, so where one is + # set it names a nearer network than the stored context does. Reading it + # first keeps a nested call, such as a mark inside `filter_ties()`, from + # resolving against the outer network. tg <- tryCatch({ if (!tidygraph::.graph_context$free()) { @@ -146,7 +131,28 @@ active_network <- function(required = NULL) { }, error = function(e) NULL) - tg + if (!is.null(tg)) return(tg) + + ctx <- get_active_context() + + if (!is.null(ctx$data)) { + + if (!is.null(required) && + !identical(ctx$active, required)) { + snet_abort( + paste0( + "This call requires ", + if (identical(required, "edges")) "ties" else required, + " to be active" + ), + call. = FALSE + ) + } + + return(ctx$data) + } + + NULL } with_active_context <- function(data, active, expr) { diff --git a/cran-comments.md b/cran-comments.md index ebced6e2..adaa52bd 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -8,3 +8,20 @@ ## R CMD check results 0 errors | 0 warnings | 0 notes + +## Reverse dependencies + +The auto-check of 2.3.0 reported new failures in `autograph` and `netrics`. +I maintain both packages. + +This version fixes the three causes that were bugs in `manynet`: + +* `tie_attribute()` and `node_attribute()` aborted on a stocnet object where + the caller named no attribute, which the `{igraph}` method allows. +* A mark inside `filter_ties()` read the outer network, not the filtered one. +* `is_longitudinal()` marked a network whose ties carry no moment. + +The remaining failures are calls in the released `autograph` 1.1.2 to +`to_no_isolates()`, which 2.3.0 deprecates, and to arguments that `autograph` +itself deprecates. `autograph` 1.2.0 removes them and passes against this +version with 0 failures. It is submitted separately. diff --git a/tests/testthat/test-manip_grab.R b/tests/testthat/test-manip_grab.R index 4fce080d..90245eec 100644 --- a/tests/testthat/test-manip_grab.R +++ b/tests/testthat/test-manip_grab.R @@ -195,3 +195,36 @@ test_that("describe_ties counts the parallel ties", { expect_no_match(describe_ties(ison_adolescents), "parallel") expect_no_match(describe_ties(ison_monks), "parallel") }) + +test_that("tie_attribute and node_attribute return every attribute where none is named", { + # `igraph::edge_attr()` and `igraph::vertex_attr()` do this, so the other + # classes' methods do too. + expect_type(tie_attribute(irps_nuclear), "list") + expect_equal(names(tie_attribute(irps_nuclear)), + names(tie_attribute(as_igraph(irps_nuclear)))) + expect_equal(names(node_attribute(as_igraph(irps_nuclear))), + net_node_attributes(as_igraph(irps_nuclear))) + # 'from' and 'to' identify a tie rather than describe it, so a stocnet + # object reports the attributes alone. + expect_false(any(c("from", "to") %in% names(tie_attribute(irps_nuclear)))) + expect_equal(names(tie_attribute(as_network(irps_nuclear))), + net_tie_attributes(as_network(irps_nuclear))) +}) + +test_that("a mark inside filter_ties reads the filtered network", { + # `filter_ties()` on an igraph object sets a tidygraph context, which names + # a nearer network than any context an outer stocnet call has stored. + like <- to_uniplex(ison_monks, "like") + seen <- NULL + count_ties <- function() { + seen <<- as.integer(net_ties(expect_ties())) + TRUE + } + wave1 <- filter_ties(as_igraph(like), time == 1) + expect_lt(net_ties(wave1), net_ties(like)) + mutate_ties(like, all = { + filter_ties(wave1, count_ties()) + TRUE + }) + expect_equal(seen, as.integer(net_ties(wave1))) +}) diff --git a/tests/testthat/test-mark_is.R b/tests/testthat/test-mark_is.R index 749e0678..b30e445d 100644 --- a/tests/testthat/test-mark_is.R +++ b/tests/testthat/test-mark_is.R @@ -70,3 +70,17 @@ test_that("is_connected respects connectivity", { expect_true(is_connected(ison_adolescents, connectivity = "strong")) expect_error(is_connected(ison_adolescents, connectivity = "bloop")) }) + +test_that("is_longitudinal does not mark a network whose ties carry no moment", { + # A panel re-observes the ties, so the ties carry the stamp. A diffusion + # records only how the nodes change, on a network whose ties never change, + # so it is not a panel. + diff <- play_diffusion(create_ring(12), seeds = 1) + expect_true(is_changing(diff)) + expect_equal(net_tie_attributes(diff), character(0)) + expect_false(is_longitudinal(diff)) + # The panels still mark TRUE, whether or not they also record changes. + expect_true(is_longitudinal(ison_monks)) + expect_true(is_longitudinal(ison_classmates)) + expect_true(is_longitudinal(fict_starwars)) +}) From 8b9c223eedffdcfcc398378d3b455d4e004a399e Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 26 Aug 2026 16:06:00 +0200 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 57a0e1b9..10dc5ff4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,7 +6,7 @@ ## Measures -- Fixed `tie_attribute.stocnet)` and `node_attribute.stocnet())` +- Fixed `tie_attribute.stocnet()` and `node_attribute.stocnet()` - Returns every attribute where no attribute is named, as `.igraph` method already did - Reports tie attributes without 'from' and 'to', which identify a tie rather than describe it