Skip to content

Commit

Permalink
Robustified mm() against levels2=I(...) specification with invalid le…
Browse files Browse the repository at this point in the history
…vels and improved documentation and examples.
  • Loading branch information
krivit committed Oct 11, 2024
1 parent cf3ef52 commit b57571f
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 17 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Package: ergm
Version: 4.7-7433
Version: 4.7-7435
Date: 2024-10-11
Title: Fit, Simulate and Diagnose Exponential-Family Models for Networks
Authors@R: c(
Expand Down
29 changes: 21 additions & 8 deletions R/InitErgmTerm.R
Original file line number Diff line number Diff line change
Expand Up @@ -3512,22 +3512,26 @@ InitErgmTerm.meandeg<-function(nw, arglist, ...) {

#' @templateVar name mm
#' @title Mixing matrix cells and margins
#' @description `attrs` is the rows of the mixing matrix and whose RHS gives
#' that for its columns. A one-sided formula (e.g.,
#' `~A` ) is symmetrized (e.g., `A~A` ). A two-sided formula with a dot on one side
#' calculates the margins of the mixing matrix, analogously to `nodefactor` , with
#' `A~.` calculating the row/sender/b1 margins and `.~A`
#' calculating the column/receiver/b2 margins.
#' @description `attrs` is the rows of the mixing matrix and whose RHS
#' gives that for its columns (which may be different). A one-sided
#' formula (e.g., `~A` ) is symmetrized (e.g., `A~A` ). A two-sided
#' formula with a dot on one side calculates the margins of the
#' mixing matrix, analogously to `nodefactor` , with `A~.`
#' calculating the row/sender/b1 margins and `.~A` calculating the
#' column/receiver/b2 margins. If row and column attributes are the
#' same and the network is undirected, only the cells at or above
#' the diagonal (where \eqn{\text{row} \le \text{column}}{row <=
#' column}) will be calculated.
#'
#' @usage
#' # binary: mm(attrs, levels=NULL, levels2=-1)
#'
#' @param attrs a two-sided formula whose LHS gives the attribute or
#' attribute function (see Specifying Vertex attributes and Levels (`?nodal_attributes`) for details.) for the rows of the mixing matrix and whose RHS gives
#' attribute function (see Specifying Vertex attributes and Levels ([`?nodal_attributes`][nodal_attributes]) for details.) for the rows of the mixing matrix and whose RHS gives
#' for its columns. A one-sided formula (e.g., `~A`) is symmetrized (e.g., `A~A`)
#' @templateVar explain subset of rows and columns to be used.
#' @template ergmTerm-levels-doco
#' @param levels2 which specific cells of the matrix to include
#' @param levels2 which specific cells of the matrix to include; [`?nodal_attributes`][nodal_attributes] for details
#'
#' @template ergmTerm-general
#'
Expand Down Expand Up @@ -3626,6 +3630,15 @@ InitErgmTerm.mm<-function (nw, arglist, ..., version=packageVersion("ergm")) {
levels2sel <- ergm_attr_levels(a$levels2, list(row=attrval$row$val, col=attrval$col$val), nw, levels=levels2)
if(length(levels2sel) == 0) return(NULL)
levels2codes <- levels2codes[match(levels2sel,levels2, NA)]
levels2missing <- map_lgl(levels2codes, is.null)
if(any(levels2missing) && symm){
rev_matches <- levels2sel[levels2missing] %>% map(function(cell) list(row = cell$col, col = cell$row)) %in% levels2
if(any(rev_matches)){
rev_matches <- map_chr(levels2sel[levels2missing][rev_matches], function(cell) paste0("[", cell$row, ",", cell$col, "]"))
ergm_Init_warn("Selected cells ", paste.and(sQuote(rev_matches)), " are redundant (below the diagonal) in the mixing matrix and will have count 0.")
}
}
levels2codes[levels2missing] <- list(list(row = 0, col = 0))
levels2 <- levels2sel; rm(levels2sel)

# Construct the level names
Expand Down
4 changes: 4 additions & 0 deletions R/get.node.attr.R
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ get.node.attr <- function(nw, attrname, functionname=NULL, numeric=FALSE) {
#' levels2 = I(list(list(row="M",col="M"),
#' list(row="M",col="F"),
#' list(row="F",col="M")))))
#' # Note the warning: in an undirected network with identical row and
#' # column attributes, the mixing matrix is symmetric and only the
#' # upper triangle (where row < column) is valid, so the [M,F] cell
#' # will get a statistic of 0 with a warning.
#'
#' # mm() term allows two-sided attribute formulas with different attributes:
#' summary(faux.mesa.high~mm(Grade~Race, levels2=TRUE))
Expand Down
20 changes: 12 additions & 8 deletions man/mm-ergmTerm-0264ed3f.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions man/nodal_attributes.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions tests/testthat/test-term-mm.R
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,21 @@ test_that("Undirected mm() summary with level2 filter by numeric matrix", {
expect_equal(s.ab2, fmh.mm.Race[5], ignore_attr=TRUE)
})

test_that("Undirected mm() summary with level2 AsIs", {
s <- summary(fmh~mm("Sex",
levels2 = I(list(list(row="M",col="M"),
list(row="F",col="M")))))
expect_equal(s, c(`mm[Sex=M,Sex=M]` = 50, `mm[Sex=F,Sex=M]` = 71))

expect_warning(
s <- summary(fmh~mm("Sex",
levels2 = I(list(list(row="M",col="M"),
list(row="M",col="F"), # Invalid level
list(row="F",col="M"))))),
"In term 'mm' in package 'ergm': Selected cells '\\[M,F\\]' are redundant \\(below the diagonal\\) in the mixing matrix and will have count 0\\.")
expect_equal(s, c(`mm[Sex=M,Sex=M]` = 50, `mm[Sex=M,Sex=F]` = 0, `mm[Sex=F,Sex=M]` = 71))
})

test_that("Directed mm() ERGM with level2 filter", {
e.ab2 <- ergm(samplike ~ mm("Trinity", levels2=-(3:9)))
expect_equal(coef(e.ab2),
Expand Down

0 comments on commit b57571f

Please sign in to comment.