Skip to content

Commit 383be1b

Browse files
committed
Layer() now ensures that the layer names are set early (simplifying name map extraction), and performs some additional checks around network attributes, including checking for reserved vertex attributes.
1 parent 5db0155 commit 383be1b

4 files changed

Lines changed: 46 additions & 33 deletions

File tree

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: ergm.multi
2-
Version: 0.3.0-4261
3-
Date: 2026-03-20
2+
Version: 0.3.0-4275
3+
Date: 2026-03-21
44
Title: Fit, Simulate and Diagnose Exponential-Family Models for Multiple or Multilayer Networks
55
Authors@R: c(person(c("Pavel", "N."), "Krivitsky", role=c("aut","cre"), email="pavel@statnet.org", comment=c(ORCID="0000-0002-9101-3362")),
66
person(c("Mark", "S."), "Handcock", role=c("ctb"), email="handcock@stat.ucla.edu"),

R/multilayer.R

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,18 @@ network.layercount <- function(x, ...) {
2727
map(nwl, `%ergmlhs%`, nattr) %>% map(NVL, ~.) %>% map(empty_env) %>% all_identical
2828
}
2929

30-
31-
.varying_attributes <- function(nwl, lister, getter, type, ignore = c()){
32-
attrs1 <- lister(nwl[[1]]) %>% setdiff(ignore)
33-
attrs <- nwl[-1] %>% map(lister) %>% map(setdiff, ignore)
30+
.varying_attributes <- function(nwl, lister, getter, type, ignore = c(), reserved = c()) {
31+
attrs1 <- lister(nwl[[1]]) %>% setdiff(c(ignore, reserved))
32+
attrs <- nwl[-1] %>% map(lister) %>% map(setdiff, c(ignore, reserved))
3433
extra_attrs <- attrs %>% unlist() %>% unique() %>% setdiff(attrs1)
3534

35+
for (res in reserved) {
36+
found <- names(nwl)[map_lgl(c(list(attrs1), attrs), \(a) res %in% a)]
37+
if (length(found)) warning(type, " attribute ", dQuote(res), " found in layer(s) ", dQuote(found), " is reserved by ", sQuote("Layer()"), " and will be overwritten.", immediate. = TRUE)
38+
}
39+
3640
if(length(extra_attrs))
37-
message(type, " attribute(s) ", paste.and(sQuote(extra_attrs)), " are not found in the first layer and will not be visible to the ", sQuote("formula"), " in ", sQuote("L(formula, Ls)"), ".")
41+
message(type, " attribute(s) ", paste.and(dQuote(extra_attrs)), " are not found in the first layer and will not be visible to the ", sQuote("formula"), " in ", sQuote("L(formula, Ls)"), ".")
3842

3943
common_attrs <- attrs %>% map(intersect, attrs1)
4044
differing <-
@@ -44,20 +48,16 @@ network.layercount <- function(x, ...) {
4448
function(nw, al) if(a %in% al) identical(getter(nw, a, unlist = FALSE), getter(nwl[[1]], a, unlist = FALSE)) else TRUE
4549
) %>% all())
4650
if(any(differing))
47-
message(type, " attribute(s) ", paste.and(sQuote(attrs1[differing])), " have values different from those in the first layer; ", sQuote("formula"), " in ", sQuote("L(formula, Ls)"), " will not see them.")
51+
message(type, " attribute(s) ", paste.and(dQuote(attrs1[differing])), " have values different from those in the first layer; ", sQuote("formula"), " in ", sQuote("L(formula, Ls)"), " will not see them.")
4852

4953
length(extra_attrs) || any(differing)
5054
}
5155

5256

5357
.layer_namemap <- function(nw) {
5458
if (is(nw, "network")) {
55-
nwnames <- get_combining_attr(nw, ".LayerName", missing = "NULL")
56-
if (is.numeric(nwnames)) nwnames <- NULL
57-
nwids <- get_combining_attr(nw, ".LayerID")
58-
59-
o <- structure(nwids, names = nwnames %||% as.character(nwids))
60-
o[!duplicated(o)]
59+
nwl <- subnetwork_templates(nw, ".LayerID", ".LayerName", copy.ergmlhs = c())
60+
setNames(seq_along(nwl), names(nwl))
6161
} else nw
6262
}
6363

@@ -512,9 +512,22 @@ Layer <- function(..., .symmetric=NULL, .bipartite=NULL, .active=NULL){
512512

513513
}else stop("Unrecognized format for multilayer specification. See help for information.")
514514

515+
# Perform some checks and imputations for layer names.
516+
if (is.null(nnames <- names(nwl))) nnames <- as.character(seq_along(nnames))
517+
else if (any(blank <- nnames == "")) {
518+
message("Layer(s) ", paste.and(which(blank)), " do not have specified names; they have been imputed with the corresponding layer number.")
519+
nnames[blank] <- as.character(seq_along(nnames)[blank])
520+
}
521+
if(any(weird <-
522+
regexpr("^[0-9]+$", nnames) != -1L # Names that are integers are potentially problematic,
523+
& nnames != seq_along(nnames) # but not if they happen to match layer IDs.
524+
)) warning("Using numeric layer names (", paste.and(dQuote(nnames[weird])), ") is ambiguous.", immediate. = TRUE)
525+
if (anyDuplicated(nnames)) stop("Duplicate layer names.")
526+
names(nwl) <- nnames
527+
515528
## If network or vertex attributes differ from the first network, warn.
516529
.varying_attributes(nwl, list.network.attributes, get.network.attribute, "Network", ignore = c("directed", "bipartite", "mnext", ".block_blacklist"))
517-
.varying_attributes(nwl, list.vertex.attributes, get.vertex.attribute, "Vertex", ignore = c(".undirected", ".bipartite", ".ubid"))
530+
.varying_attributes(nwl, list.vertex.attributes, get.vertex.attribute, "Vertex", ignore = c(".ubid"), reserved = c(".undirected", ".bipartite"))
518531

519532
if(!is.null(.active)){
520533
if(!is.list(.active) || length(.active) != length(nwl)) stop(sQuote(".active="), " argument if given must be a list of attribute specifications, one for each layer.")
@@ -555,19 +568,6 @@ Layer <- function(..., .symmetric=NULL, .bipartite=NULL, .active=NULL){
555568
# nwl is now a list of networks with homogeneous directedness, some
556569
# networks tagged with vertex attribute .undirected.
557570

558-
# Perform some checks and imputations for layer names.
559-
nnames <- names(nwl)
560-
if(!is.null(nnames) && any(blank<-(nnames==""))){
561-
warning("Only some of the layers have specified names; they have been imputed with the corresponding layer number.")
562-
nnames[blank] <- as.character(seq_along(nnames)[blank])
563-
}
564-
if(any(
565-
regexpr('^[0-9]+$',nnames)!=-1 # Names that are integers are potentially problematic,
566-
& nnames!=seq_along(nnames) # but not if they happen to match layer IDs.
567-
)) warning("Using numeric layer names is ambiguous.")
568-
if(anyDuplicated(nnames)) stop("Duplicate layer names.")
569-
names(nwl) <- nnames
570-
571571
if(!.same_constraints(nwl, "constraints")) stop("Layers have differing constraint structures. This is not supported at this time.")
572572
if(!.same_constraints(nwl, "obs.constraints")) stop("Layers have differing observation processes. This is not supported at this time.")
573573

tests/testthat/test-multilayer-heterogeneous-directedness.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ test_that("multilayer heterogeneous layers messages", {
2828

2929
expect_message(expect_message(
3030
Layer(nw12, nw1),
31-
"Vertex attribute\\(s\\) 'mode' are not found in the first layer.*"),
32-
"Network attribute\\(s\\) 'nattr' are not found in the first layer.*"
31+
"Vertex attribute\\(s\\) \"mode\" are not found in the first layer.*"),
32+
"Network attribute\\(s\\) \"nattr\" are not found in the first layer.*"
3333
)
3434

3535
nw12 %v% "mode" <- rep(1:2,c(15,5))
3636
nw12 %n% "nattr" <- "def"
3737
expect_message(expect_message(
3838
Layer(nw1, nw12, .active=list(~mode==1, ~TRUE)),
39-
"Vertex attribute\\(s\\) 'mode' have values different from those in the first layer.*"),
40-
"Network attribute\\(s\\) 'nattr' have values different from those in the first layer.*"
39+
"Vertex attribute\\(s\\) \"mode\" have values different from those in the first layer.*"),
40+
"Network attribute\\(s\\) \"nattr\" have values different from those in the first layer.*"
4141
)
4242
})

tests/testthat/test-multilayer.R

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,20 @@ ctrl <- control.simulate.formula(MCMC.burnin=n^2*2, MCMC.interval=n)
3232

3333
test_that("twostarL statistics for directed networks", {
3434
nw1 <- nw2 <- network.initialize(n, directed = TRUE)
35-
lnw <- Layer(nw1, nw2)
35+
# Duplicate layer name.
36+
expect_warning(expect_message(expect_error(lnw <- Layer(nw1, `1` = nw2),
37+
"Duplicate layer names."),
38+
"Layer\\(s\\) 1 do not have specified names.*"),
39+
"Using numeric layer names \\(\"1\"\\) is ambiguous.")
40+
41+
# Reserved attribute and non-duplicate numeric layer name.
42+
nw1 %v% ".undirected" <- FALSE
43+
expect_message(expect_warning(lnw <- Layer(nw1, `123` = nw2),
44+
"Using numeric layer names \\(\"123\"\\) is ambiguous."),
45+
"Layer\\(s\\) 1 do not have specified names.*")
46+
47+
# OK numeric layer name.
48+
expect_message(lnw <- Layer(`1` = nw1, nw2), "Layer\\(s\\) 2 do not have .*")
3649

3750
sim <- suppressWarnings(simulate(lnw~
3851
twostarL(c(~`1`,~`2`), "out",FALSE)+

0 commit comments

Comments
 (0)