Skip to content

Commit a3feb65

Browse files
committed
Implemented a new term, Lag(), to allow networks to be regressed on earlier networks' change statistics; added a new keyword, "temporal".
fixes statnet/ergm-private#148
1 parent e9ebf4a commit a3feb65

9 files changed

+188
-10
lines changed

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Package: tergm
2-
Version: 4.2.2-2625
2+
Version: 4.2.2-2626
33
Date: 2026-02-24
44
Title: Fit, Simulate and Diagnose Models for Network Evolution Based on Exponential-Family Random Graph Models
55
Authors@R: c(
@@ -47,7 +47,7 @@ Description: An integrated set of extensions to the 'ergm' package to analyze an
4747
License: GPL-3 + file LICENSE
4848
URL: https://statnet.org
4949
VignetteBuilder: rmarkdown, knitr
50-
RoxygenNote: 7.3.2.9000
50+
RoxygenNote: 7.3.3.9000
5151
Roxygen: list(markdown = TRUE)
5252
Config/testthat/parallel: true
5353
Config/testthat/edition: 3

R/InitErgmTerm.netseries.R

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@
88
# Copyright 2008-2025 Statnet Commons
99
################################################################################
1010

11-
.call_N <- function(term, nw, arglist, ..., env=baseenv()){
11+
.call_N <- function(term, nw, arglist, ..., env=baseenv(),
12+
ownargs = list(varnames = "formula", vartypes = "formula", defaultvalues = list(NULL), required = TRUE)) {
1213
a <- check.ErgmTerm(nw, arglist,
13-
varnames = c("formula", "lm", "subset", "weights", "contrasts", "offset", "label"),
14-
vartypes = c("formula", "formula", "formula,logical,numeric,expression,call", "formula,logical,numeric,expression,call", "list", "formula,logical,numeric,expression,call", "character"),
15-
defaultvalues = list(NULL, ~1, TRUE, 1, NULL, NULL, NULL),
16-
required = c(TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE))
14+
varnames = c(ownargs$varnames, "lm", "subset", "weights", "contrasts", "offset", "label"),
15+
vartypes = c(ownargs$vartypes, "formula", "formula,logical,numeric,expression,call", "formula,logical,numeric,expression,call", "list", "formula,logical,numeric,expression,call", "character"),
16+
defaultvalues = c(ownargs$defaultvalues, list(~1, TRUE, 1, NULL, NULL, NULL)),
17+
required = c(ownargs$required, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE))
1718

1819
f <- a$formula
19-
ult(f) <- call(paste0(term,"1"), as.formula(call("~",ult(f)), env=env)) # e.g., a~b -> a~term1(~b)
20+
# e.g., a~b -> a~term1(~b)
21+
ult(f) <- as.call(c(list(paste0(term,"1"),
22+
as.formula(call("~",ult(f)), env=env)),
23+
a[setdiff(ownargs$varnames, "formula")]))
2024
environment(f) <- environment(a$formula)
2125
a$formula <- f
2226

@@ -33,6 +37,7 @@
3337
#' @template ergmTerm-rdname
3438
#' @usage NULL
3539
#' @template ergmTerm-N-arguments
40+
#' @concept temporal
3641
InitErgmTerm.Form <- function(nw, arglist, ...){
3742
if(!is(nw, "tergm_NetSeries")) `InitErgmTerm.Form (dynamic)`(nw = nw, arglist = arglist, ...)
3843
else .call_N("Form", nw, arglist, ...)
@@ -62,6 +67,7 @@ InitErgmTerm.Form1 <- function(nw, arglist, ...){
6267
#' @template ergmTerm-rdname
6368
#' @usage NULL
6469
#' @template ergmTerm-N-arguments
70+
#' @concept temporal
6571
InitErgmTerm.Persist <- function(nw, arglist, ...) {
6672
if(!is(nw, "tergm_NetSeries")) `InitErgmTerm.Persist (dynamic)`(nw = nw, arglist = arglist,...)
6773
else .call_N("Persist", nw, arglist, ...)
@@ -113,6 +119,7 @@ InitErgmTerm.Diss1 <- function(nw, arglist, ..., env){
113119
#' @template ergmTerm-rdname
114120
#' @usage NULL
115121
#' @template ergmTerm-N-arguments
122+
#' @concept temporal
116123
InitErgmTerm.Change <- function(nw, arglist, ...) {
117124
if(!is(nw, "tergm_NetSeries")) `InitErgmTerm.Change (dynamic)`(nw = nw, arglist = arglist, ...)
118125
else .call_N("Change", nw, arglist, ...)
@@ -165,6 +172,7 @@ InitErgmTerm.Change1 <- function(nw, arglist, ...){
165172
#' @rawNamespace import(ergm.multi, except=c("snctrl"))
166173
#'
167174
#' @concept operator
175+
#' @concept temporal
168176
#' @concept durational
169177
InitErgmTerm.Cross <- function(nw, arglist, ..., env=baseenv()) {
170178
if (!is(nw, "tergm_NetSeries")) `InitErgmTerm.Cross (dynamic)`(nw = nw, arglist = arglist, ...)
@@ -181,3 +189,104 @@ InitErgmTerm.Cross1 <- function(nw, arglist, ...){
181189

182190
ergm_model(a$formula, nw, ..., terms.only=TRUE)
183191
}
192+
193+
194+
#' @templateVar name Lag
195+
#' @title An edge covariate on the previous network's statistics
196+
#' @description This term takes an ERGM formula that is evaluated on a
197+
#' previous time step's network; the change statistics (optionally
198+
#' transformed) are used as an edge covariate for the current
199+
#' network. That is, a statistic of the form \eqn{\sum_{i,j}
200+
#' y^{t}_{i,j} \Delta_{i,j} g(y^{t-1})} or a transformed variant
201+
#' \eqn{\sum_{i,j} y^{t}_{i,j} f(\Delta_{i,j} g(y^{t-1}),
202+
#' y^{t-1}_{i,j})}, where \eqn{Delta_{i,j} g(y) = g(y+(i,j)) -
203+
#' g(y-(i,j))}.
204+
#'
205+
#' @usage
206+
#' # binary: Lag(
207+
#' # formula,
208+
#' # lag = 1,
209+
#' # transform = NULL,
210+
#' # lm = ~1,
211+
#' # subset = TRUE,
212+
#' # weights = 1,
213+
#' # contrasts = NULL,
214+
#' # offset = 0,
215+
#' # label = NULL
216+
#' # )
217+
#' @template ergmTerm-formula
218+
#' @param lag how many time steps to look back; at this time, only 1
219+
#' is implemented.
220+
#' @param transform a `function(x, y)` given an array of covariates
221+
#' and the sociomatrix of the *previous* network to optionally
222+
#' transform `x`, `NULL` to leave unchanged, or a character string
223+
#' indicating a preset; current presets include \describe{
224+
#'
225+
#' \item{`"signed"`}{multiplies each change statistic by \eqn{(2
226+
#' y^{t-1}_{i,j} - 1)}}
227+
#'
228+
#' \item{`"nonzero"`, "`positive`", "`negative`"}{indicator of whether
229+
#' the change statistic has the respective value}
230+
#'
231+
#' }
232+
#' @template ergmTerm-N-arguments
233+
#'
234+
#' @template ergmTerm-general
235+
#'
236+
#' @references
237+
#'
238+
#' Almquist, Z. W. and Butts, C. T. (2014). Logistic Network
239+
#' Regression for Scalable Analysis of Networks with Joint Edge/Vertex
240+
#' Dynamics. *Sociological Methodology*, 44(1),
241+
#' 273-321. \doi{10.1177/0081175013520159}
242+
#'
243+
#' @concept operator
244+
#' @concept temporal
245+
#' @concept dyad-independent
246+
InitErgmTerm.Lag <- function(nw, arglist, ..., env=baseenv()) {
247+
if (!is(nw, "tergm_NetSeries")) ergm_Init_stop("This term does not support non-netseries input at this time.")
248+
ownargs <- list(varnames = c("formula", "lag", "transform"),
249+
vartypes = c("formula", "numeric", "function,character"),
250+
defaultvalues = list(NULL, 1, function(x, ...) x),
251+
required = c(TRUE, FALSE, FALSE))
252+
.call_N("Lag", nw, arglist, ownargs = ownargs, ..., env=env)
253+
}
254+
255+
InitErgmTerm.Lag1 <- function(nw, arglist, ...) {
256+
a <- check.ErgmTerm(nw, arglist,
257+
varnames = c("formula", "lag", "transform"),
258+
vartypes = c("formula", "numeric", "function,formula,character"),
259+
defaultvalues = list(NULL, 1, NULL),
260+
required = c(TRUE, FALSE, FALSE))
261+
262+
NVL(a$transform) <- function(x, ...) x
263+
264+
if (is.character(a$transform)) {
265+
presets <- c("signed", "nonzero", "positive")
266+
a$transform <- switch(match.arg(a$transform, presets),
267+
signed = function(x, y, ...) c(2*y-1)*x,
268+
nonzero = function(x, ...) x != 0,
269+
positive = function(x, ...) x > 0,
270+
negative = function(x, ...) x < 0,
271+
ergm_Init_stop(sQuote(a$transform), " is not a valid preset; currently valid presets include ", paste.and(sQuote(presets)), "."))
272+
}
273+
274+
if (a$lag != 1) ergm_Init_stop("Lags other than 1 are not currently implemented.")
275+
276+
mple <- ergmMPLE(a$formula, basis = (nw%n%".PrevNets")[[1]],
277+
expand.bipartite = TRUE, output = "array")
278+
y <- mple$response
279+
x <- mple$predictor
280+
281+
pred <- a$transform(x = x, y = y)
282+
if (ncol(pred) == 0) ergm_Init_stop("Formula produced 0 statistics.")
283+
284+
terms <- imap(dimnames(pred)[[3]],
285+
function(nm, i) term_list(call("edgecov", x = pred[, , i], attrname = nm), env = environment(a$formula))) |>
286+
do.call(c, args = _)
287+
288+
m <- ergm_model(terms, nw, ..., terms.only = TRUE)
289+
param_names(m) <- map(list(param_names(m, canonical = FALSE), param_names(m, canonical = TRUE)),
290+
substring, 9, 10000)
291+
m
292+
}

R/zzz.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,6 @@ eval(UPDATE_MY_SCTRL_EXPR)
4949
}
5050

5151
.RegisterKeywords <- function() {
52-
ergm_keyword(name='durational', short='dur', description="", popular=TRUE, package="tergm")
52+
ergm_keyword(name = "durational", short = "dur", description = "this term is designed for temporal ERGMs with duration information", popular = TRUE, package = "tergm")
53+
ergm_keyword(name = "temporal", short = "temp", description = "this term is designed for temporal ERGMs", popular = TRUE, package = "tergm")
5354
}

man/Change-ergmTerm-4236ef3c.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/Cross-ergmTerm-03ab5578.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/Form-ergmTerm-fa13d505.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/Lag-ergmTerm-513a71d5.Rd

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/NetSeries.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/Persist-ergmTerm-34b51891.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)