Skip to content

Commit 50eec90

Browse files
authored
Merge pull request #36 from statnet/i21-gw-cutoff
Refactor *spL() terms to work like those of 'ergm' and remove *degreeL() terms.
2 parents f100e64 + dae818c commit 50eec90

31 files changed

Lines changed: 1783 additions & 3560 deletions

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-4176
3-
Date: 2025-04-08
2+
Version: 0.3.0-4188
3+
Date: 2025-06-13
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/InitErgmConstraint.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@
5858
## n <- network.size(lhs.nw)
5959
## storage.mode(n) <- "integer"
6060
## a <- c(ergm_get_vattr(attr, lhs.nw)) # Strip attributes, which confuse rle().
61-
## if(NVL(lhs.nw%n%"bipartite",0)){
62-
## bip <- lhs.nw %n% "bipartite"
61+
## if(NVL(lhs.b1.size(nw),0)){
62+
## bip <- lhs.b1.size(nw)
6363
## ea <- a[seq_len(bip)]
6464
## aa <- a[bip+seq_len(n-bip)]
6565
## if(length(rle(ea)$lengths)!=length(unique(rle(ea)$values)) || length(rle(aa)$lengths)!=length(unique(rle(aa)$values))) stop("Current implementation of block-diagonal sampling requires that the blocks of the egos and the alters be contiguous. See ", sQuote("ergmConstraint?blockdiag"), " for more information.")

R/InitErgmProposal.blockdiag.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109
#' @keywords internal
110110
#' @export
111111
ergm_block_diag_samp_info <- function(nw, a){
112-
bip <- nw %n% "bipartite"
112+
bip <- b1.size(nw)
113113
if(bip){
114114
ea <- a[seq_len(bip)]
115115
aa <- a[bip+seq_len(network.size(nw)-bip)]
@@ -135,7 +135,9 @@ ergm_block_diag_samp_info <- function(nw, a){
135135
w <- cumsum(a$lengths*(a$lengths-1)) # cumulative block weights ~ # dyads in the block
136136
w <- w/max(w)
137137
# Note that this automagically takes care of singleton blocks by giving them weight 0.
138-
out <- list(nblk=as.integer(length(w)), pos=as.integer(b), cumwt=as.double(w), ndyads=sum(as.double(a$lengths*(a$lengths-1)/(if(is.directed(nw)) 1 else 2))))
138+
out <- list(nblk = as.integer(length(w)), pos = as.integer(b),
139+
cumwt = as.double(w),
140+
ndyads = sum(choose(a$lengths, 2L) * (1L + is.directed(nw))))
139141
}
140142
structure(out, class="ergm_block_diag_samp_info")
141143
}

R/InitErgmTerm.R

Lines changed: 0 additions & 1086 deletions
This file was deleted.

R/InitErgmTerm.dgw_sp.R

Lines changed: 197 additions & 369 deletions
Large diffs are not rendered by default.

R/InitErgmTerm.multilayer.R

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ direct.network <- function(x, rule=c("both", "upper", "lower")){
217217
upper = cbind(pmin(el[,1],el[,2]),pmax(el[,1],el[,2])),
218218
lower = cbind(pmax(el[,1],el[,2]),pmin(el[,1],el[,2])))
219219

220-
o <- network.initialize(network.size(x), directed=TRUE, bipartite=x%n%"bipartite", loops=has.loops(x), hyper=is.hyper(x), multiple=is.multiplex(x))
220+
o <- network.initialize(network.size(x), directed = TRUE,
221+
bipartite = b1.size(x), loops = has.loops(x),
222+
hyper = is.hyper(x), multiple = is.multiplex(x))
221223
o <- network.edgelist(el, o)
222224
nvattr.copy.network(o, x)
223225
}
@@ -482,7 +484,7 @@ Layer <- function(..., .symmetric=NULL, .bipartite=NULL, .active=NULL){
482484
}
483485

484486
# nwl may now be a list with networks of heterogeneous bipartitedness.
485-
bip <- sapply(nwl, `%n%`, "bipartite") %>% sapply(NVL, 0L)
487+
bip <- sapply(nwl, b1.size) %>% sapply(NVL, 0L)
486488
blockout <- if(all_identical(bip)) rep(FALSE, length(nwl)) else bip
487489

488490
nwl <- Map(function(nw, b){
@@ -1033,3 +1035,120 @@ InitErgmTerm.twostarL<-function(nw, arglist, ...) {
10331035
iinputs <- c(typeID, a$distinct)
10341036
list(name="twostarL", coef.names=coef.names, iinputs=iinputs, auxiliaries=auxiliaries, minval=0, dependence=TRUE)
10351037
}
1038+
1039+
################################################################################
1040+
1041+
#' @templateVar name mutualL
1042+
#' @title Mutuality
1043+
#' @description In binary ERGMs, equal to the number of
1044+
#' pairs of actors \eqn{i} and \eqn{j} for which \eqn{(i{\rightarrow}j)}{(i,j)}
1045+
#' and \eqn{(j{\rightarrow}i)}{(j,i)} both exist.
1046+
#'
1047+
#' @details This term can only be used with directed networks.
1048+
#'
1049+
#' @usage
1050+
#' # binary: mutualL(same=NULL, diff=FALSE, by=NULL, keep=NULL, Ls=NULL)
1051+
#' @param same optional argument. If passed the name of a vertex attribute,
1052+
#' only mutual pairs that match on the attribute are counted. Only one of `same`
1053+
#' or `by` may be used. If both parameters are passed, `same` takes precedent. This
1054+
#' parameter is affected by `diff`.
1055+
#' @param diff separate counts for each unique matching value can be obtained by using
1056+
#' `diff=TRUE` with `same`.
1057+
#' @param by each node is counted separately for each mutual pair in which it
1058+
#' occurs and the counts are tabulated by unique values of the attribute if
1059+
#' passed the name of a vertex attribute. This means that the sum of the mutual statistics when `by` is used
1060+
#' will equal twice the standard mutual statistic. Only one of `same`
1061+
#' or `by` may be used. If both parameters are passed, `same` takes precedent. This
1062+
#' parameter is not affected by `diff`.
1063+
#' @param keep a numerical vector to specify which statistics should be kept whenever the `mutual` term would
1064+
#' ordinarily result in multiple statistics.
1065+
#' @templateVar Ls.howmany one or two
1066+
#' @templateVar Ls.interp . If given, the statistic will count the number of dyads where a tie in `Ls[[1]]` reciprocates a tie in `Ls[[2]]` and vice versa. (Note that dyad that has mutual ties in `Ls[[1]]` and in `Ls[[2]]` will add 2 to this statistic.) If a formula is given, it is replicated.
1067+
#' @template ergmTerm-Ls
1068+
#'
1069+
#' @template ergmTerm-general
1070+
#'
1071+
#' @concept directed
1072+
#' @concept frequently-used
1073+
#' @concept layer-aware
1074+
InitErgmTerm.mutualL<-function (nw, arglist, ...) {
1075+
## Check the network and arguments to make sure they are appropriate.
1076+
a <- check.ErgmTerm(nw, arglist, directed=TRUE, bipartite=NULL,
1077+
varnames = c("same", "by", "diff", "keep", "Ls"),
1078+
vartypes = c("character", "character", "logical", "numeric", "formula,list"),
1079+
defaultvalues = list(NULL, NULL, FALSE, NULL, NULL),
1080+
required = c(FALSE, FALSE, FALSE, FALSE, FALSE))
1081+
1082+
assert_LHS_Layer(nw)
1083+
1084+
## Process the arguments
1085+
if (!is.null(a$same) || !is.null(a$by)) {
1086+
if (!is.null(a$same)) {
1087+
attrname <- a$same
1088+
if (!is.null(a$by))
1089+
warning("Ignoring 'by' argument to mutual because 'same' exists", call.=FALSE)
1090+
}else{
1091+
attrname <- a$by
1092+
}
1093+
nodecov <- get.node.attr(nw, attrname)
1094+
u <- NVL(a$levels, sort(unique(nodecov)))
1095+
if (!is.null(a$keep)) {
1096+
u <- u[a$keep]
1097+
}
1098+
# Recode to numeric
1099+
nodecov <- match(nodecov,u,nomatch=length(u)+1)
1100+
# All of the "nomatch" should be given unique IDs so they never match:
1101+
dontmatch <- nodecov==(length(u)+1)
1102+
nodecov[dontmatch] <- length(u) + (1:sum(dontmatch))
1103+
ui <- seq(along=u)
1104+
}
1105+
1106+
### Construct the list to return
1107+
if (!is.null(a$same) || !is.null(a$by)) {
1108+
if (is.null(a$same)) {
1109+
coef.names <- paste("mutual.by", attrname, u, sep=".")
1110+
inputs <- c(ui, nodecov)
1111+
}else{
1112+
if (a$diff) {
1113+
coef.names <- paste("mutual.same", attrname, u, sep=".")
1114+
inputs <- c(ui, nodecov)
1115+
}else{
1116+
coef.names <- paste("mutual", attrname, sep=".")
1117+
inputs <- nodecov
1118+
}
1119+
}
1120+
if (is.null(a$same) && !is.null(a$by)) {
1121+
name <- "mutual_by_attr"
1122+
}else{
1123+
name <- "mutual"
1124+
}
1125+
}else{
1126+
name <- "mutual"
1127+
coef.names <- "mutual"
1128+
inputs <- NULL
1129+
}
1130+
1131+
maxval <- network.dyadcount(nw,FALSE)/2
1132+
1133+
Ls <- .set_layer_namemap(a$Ls, nw)
1134+
if(is(Ls,"formula")) Ls <- list(Ls)
1135+
L1 <- Ls[[1]]
1136+
L2 <- Ls[[2]]
1137+
if(!is.null(L1) || !is.null(L2)){
1138+
NVL(L1) <- L2
1139+
NVL(L2) <- L1
1140+
auxiliaries <- .mk_.layer.net_auxform(L1)
1141+
aux2 <- .mk_.layer.net_auxform(L2)
1142+
auxiliaries[[2]] <- call("+", auxiliaries[[2]], aux2[[2]])
1143+
name <- paste(name, "ML", sep="_")
1144+
coef.names <- .lspec_coef.namewrap(if(L1==L2) list(L1) else list(L1,L2))(coef.names)
1145+
maxval <- maxval*2
1146+
}else auxiliaries <- NULL
1147+
1148+
list(name=name, #name: required
1149+
coef.names = coef.names, #coef.names: required
1150+
inputs=inputs,
1151+
auxiliaries = auxiliaries,
1152+
minval = 0,
1153+
maxval = maxval)
1154+
}

R/InitErgmTerm.spcache.multilayer.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ InitErgmTerm..spcache.netL<-function(nw, arglist, ...){
1919

2020
type <- match.arg(tolower(a$type), c("otp","osp","isp","utp")) # ITP not included, because it's just OTP with direction reversed.
2121

22-
if(is.directed(nw)==(type=="utp")) stop("Type UTP may only be used with undirected networks, the others only with directed.")
22+
if (is.directed(nw) == (type == "utp")
23+
&& !(is.bipartite(nw) && type %in% c("osp", "isp")))
24+
stop("Type UTP may only be used with undirected networks, OSP and ISP with bipartite or directed, and the rest only with directed.")
2325

2426
dname <- paste0("_",type,"_wtnet")
25-
2627
linfo <- .sp.handle_layers(nw, a, type, FALSE)
2728

2829

R/combine.networks.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ combine_networks <- function(nwl, ignore.nattr=c("mnext"), ignore.vattr=c(), ign
234234
attrset <- if(keep.unshared.attr) union else intersect
235235

236236
ns <- sapply(nwl, network.size)
237-
es <- sapply(nwl, "%n%", "bipartite")
237+
es <- sapply(nwl, b1.size)
238238
eblks <- c(0, cumsum(es))
239239
bip <- eblks[length(eblks)]
240240
ablks <- cumsum(c(bip, ns-es))
@@ -537,7 +537,7 @@ subnetwork_templates <- function(nw, split.vattr=nw%n%".blockID.vattr", names.va
537537
.block_vertexmap <- function(nw, by=nw %n% ".blockID.vattr", same_dim=FALSE){
538538
a <- .peek_vattrv(nw, by)
539539
n <- length(a)
540-
bip <- nw %n% "bipartite"
540+
bip <- b1.size(nw)
541541
if(NVL(bip,0)){
542542
ea <- a[seq_len(bip)]
543543
aa <- a[bip+seq_len(n-bip)]

R/util.R

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## These will eventually be moved to 'statnet.common'.
2+
3+
dbl_along <- function(x) numeric(length(x))
4+
int_along <- function(x) integer(length(x))
5+
chr_along <- function(x) character(length(x))
6+
lgl_along <- function(x) logical(length(x))
7+
8+
## This should be in 'network'.
9+
10+
b1.size <- function(x) if(is.bipartite(x)) x %n% "bipartite" else FALSE
11+
b2.size <- function(x) if(is.bipartite(x)) network.size(x) - b1.size(x) else FALSE
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#' @note This term does not support multilayer networks with
2+
#' heterogeneous bipartedness. This may change in the future.

0 commit comments

Comments
 (0)