tests/testthat/test-btergm.R

context("test btergm estimation")

set.seed(12345)
networks <- list()
for(i in 1:10) {               # create 10 random networks with 10 actors
  mat <- matrix(rbinom(100, 1, .25), nrow = 10, ncol = 10)
  diag(mat) <- 0               # loops are excluded
  nw <- network::network(mat)  # create network object
  networks[[i]] <- nw          # add network to the list
}

covariates <- list()
for (i in 1:10) {              # create 10 matrices as covariate
  mat <- matrix(rnorm(100), nrow = 10, ncol = 10)
  covariates[[i]] <- mat       # add matrix to the list
}

test_that("btergm estimation works", {
  set.seed(12345)
  fit <- suppressWarnings(btergm(networks ~ edges + istar(2) + edgecov(covariates), R = 100, verbose = FALSE))
  expect_equal(round(unname(coef(fit)), 4), c(-1.1707, 0.0543, 0.0045))
  expect_equal(names(coef(fit)), c("edges", "istar2", "edgecov.covariates[[i]]"))
  expect_equal(class(fit@boot), "boot")
  expect_equal(fit@boot$R, 100)
  expect_equal(fit@R, 100)
  expect_equal(fit@nobs, 900)
  expect_equal(fit@time.steps, 10)
  expect_equal(class(fit@formula), "formula")
  expect_equal(class(fit@formula2), "character")
  expect_equal(fit@formula, as.formula("networks ~ edges + istar(2) + edgecov(covariates)"))
  expect_equal(fit@formula2, "networks[[i]] ~ edges + istar(2) + edgecov(covariates[[i]])")
  expect_equal(length(fit@response), 900)
  expect_equal(is.numeric(fit@response), TRUE)
  expect_equal(class(fit@effects), "data.frame")
  expect_equal(dim(fit@effects), c(900, 3))
  expect_equal(unique(fit@effects$edges), 1)
  expect_equal(median(fit@effects$istar2), 2)
  expect_equal(round(mean(fit@effects$`edgecov.covariates[[i]]`), 4), -0.0144)
  expect_equal(unique(fit@weights), 1)
  expect_equal(fit@auto.adjust, FALSE)
  expect_equal(fit@offset, FALSE)
  expect_equal(fit@directed, TRUE)
  expect_equal(fit@bipartite, FALSE)
  expect_equal(unname(rowSums(fit@nvertices)), c(100, 100))
  expect_equal(round(confint(fit)[1, 3], 1), -1.4)
  expect_equal(round(confint(fit)[1, 4], 1), -0.8)
  expect_equal(round(confint(fit)[2, 3], 0), 0)
  expect_equal(round(confint(fit)[2, 4], 1), 0.1)
  expect_equal(round(confint(fit)[3, 3], 1), -0.1)
  expect_equal(round(confint(fit)[3, 4], 1), 0.1)
  expect_true(all(round(confint(fit)[, 1] - confint(fit)[, 2], 1) == 0))
})

test_that("fastglm works like speedglm", {
  skip_if_not_installed("fastglm", minimum_version = "0.0.1")

  set.seed(12345)
  fit <- suppressWarnings(btergm(networks ~ edges + istar(2) + edgecov(covariates), R = 100, verbose = FALSE))
  set.seed(12345)
  fit2 <- btergm(networks ~ edges + istar(2) + edgecov(covariates), R = 100, usefastglm = TRUE, verbose = FALSE)
  expect_equal(all(round(confint(fit), 4) == round(confint(fit2), 4)), TRUE)
})

test_that("offset argument in btergm works without composition change", {
  skip_if_not_installed("fastglm", minimum_version = "0.0.1")
  set.seed(12345)
  fit1 <- suppressWarnings(btergm(networks ~ edges + istar(2) + edgecov(covariates),
                                  R = 100,
                                  offset = FALSE,
                                  usefastglm = TRUE,
                                  verbose = FALSE))
  set.seed(12345)
  fit2 <- suppressWarnings(btergm(networks ~ edges + istar(2) + edgecov(covariates),
                                  R = 100,
                                  offset = TRUE,
                                  usefastglm = TRUE,
                                  verbose = FALSE))
  expect_equal(confint(fit1), confint(fit2))
})

test_that("offset argument in btergm works with composition change", {
  skip_on_cran()
  skip_if_not_installed("fastglm", minimum_version = "0.0.1")

  # example taken from 2018 JSS article
  require("sna")
  data("knecht")

  # step 1: make sure the network matrices have node labels
  for (i in 1:length(friendship)) {
    rownames(friendship[[i]]) <- 1:nrow(friendship[[i]])
    colnames(friendship[[i]]) <- 1:ncol(friendship[[i]])
  }
  rownames(primary) <- rownames(friendship[[1]])
  colnames(primary) <- colnames(friendship[[1]])
  sex <- demographics$sex
  names(sex) <- 1:length(sex)

  # step 2: imputation of NAs and removal of absent nodes:
  suppressMessages(friendship <- handleMissings(friendship, na = 10, method = "remove"))
  suppressMessages(friendship <- handleMissings(friendship, na = NA, method = "fillmode"))

  # step 3: add nodal covariates to the networks
  for (i in 1:length(friendship)) {
    s <- adjust(sex, friendship[[i]])
    friendship[[i]] <- network(friendship[[i]])
    friendship[[i]] <- set.vertex.attribute(friendship[[i]], "sex", s)
    idegsqrt <- sqrt(degree(friendship[[i]], cmode = "indegree"))
    friendship[[i]] <- set.vertex.attribute(friendship[[i]],
                                            "idegsqrt", idegsqrt)
    odegsqrt <- sqrt(degree(friendship[[i]], cmode = "outdegree"))
    friendship[[i]] <- set.vertex.attribute(friendship[[i]],
                                            "odegsqrt", odegsqrt)
  }
  expect_equal(unname(sapply(friendship, network.size)), c(26, 26, 25, 25))

  # step 4: estimate models
  set.seed(12345)
  m1 <- btergm(friendship ~ edges + mutual + ttriple +
                 transitiveties + ctriple + nodeicov("idegsqrt") +
                 nodeicov("odegsqrt") + nodeocov("odegsqrt") +
                 nodeofactor("sex") + nodeifactor("sex") + nodematch("sex") +
                 edgecov(primary) + delrecip + memory(type = "stability"),
               R = 100, usefastglm = TRUE, offset = TRUE, verbose = FALSE)
  m2 <- btergm(friendship ~ edges + mutual + ttriple +
                 transitiveties + ctriple + nodeicov("idegsqrt") +
                 nodeicov("odegsqrt") + nodeocov("odegsqrt") +
                 nodeofactor("sex") + nodeifactor("sex") + nodematch("sex") +
                 edgecov(primary) + delrecip + memory(type = "stability"),
               R = 100, usefastglm = TRUE, offset = FALSE, verbose = FALSE)

  # test results
  expect_equal(dim(confint(m1)), c(14, 4))
  expect_equal(dim(confint(m2)), c(14, 4))
  expect_equal(all(confint(m1)[, 4] - confint(m1)[, 3] > 0), TRUE)
  expect_equal(all(confint(m2)[, 4] - confint(m2)[, 3] > 0), TRUE)
  expect_equal(m1@offset, TRUE)
  expect_equal(m2@offset, FALSE)
  expect_equal(sapply(m1@data$offsmat, sum), c(0, 51, 51))
  expect_equal(sapply(m2@data$offsmat, sum), c(0, 0, 0))
  expect_equal(unname(nobs(m1)), c(3, 1850, 100))
  expect_equal(nobs(m1), nobs(m2))
})

test_that("mtergm estimation works", {
  skip_if_not_installed("fastglm", minimum_version = "0.0.1")

  set.seed(12345)
  fit1 <- btergm(networks ~ edges + istar(2) + edgecov(covariates), usefastglm = TRUE, verbose = FALSE)
  set.seed(12345)
  fit2 <- mtergm(networks ~ edges + istar(2) + edgecov(covariates), verbose = FALSE)
  expect_equivalent(coef(fit1) - coef(fit2), rep(0, 3), tolerance = 0.05)
  expect_equivalent(coef(fit2), c(-1.17, 0.06, 0.00), tolerance = 0.05)
  expect_equivalent(fit2@se, c(0.193, 0.079, 0.074), tolerance = 0.05)
  expect_equal(class(fit2)[1], "mtergm")
  expect_equal(class(fit2@ergm), "ergm")
})

test_that("simulation of new networks works", {
  skip_if_not_installed("fastglm", minimum_version = "0.0.1")

  # for btergm
  fit1 <- btergm(networks ~ edges + istar(2) + edgecov(covariates), usefastglm = TRUE, verbose = FALSE)
  sim1 <- simulate(fit1, 5, verbose = FALSE)
  expect_equal(length(sim1), 5)
  expect_equal(class(sim1[[1]]), "network")

  # with an index
  sim1b <- simulate(fit1, index = 3, verbose = FALSE)
  expect_equal(class(sim1b), "network")

  # for mtergm
  fit2 <- mtergm(networks ~ edges + istar(2) + edgecov(covariates), verbose = FALSE)
  sim2 <- simulate(fit2, 5, verbose = FALSE)
  expect_equal(length(sim2), 5)
  expect_equal(class(sim2[[1]]), "network")
})

test_that("tbergm estimation works", {
  skip_on_cran()
  skip_if_not_installed("Bergm", minimum_version = "5.0.2")
  set.seed(12345)
  fit <- suppressWarnings(btergm(networks ~ edges + istar(2) + edgecov(covariates), R = 100, verbose = FALSE))
  suppressMessages(suppressWarnings(fit_b <- tbergm(networks ~ edges + istar(2) + edgecov(covariates), verbose = FALSE)))
  expect_s3_class(fit_b@bergm, "bergm")
  expect_length(fit_b@bergm$ess, 4)
  expect_length(fit_b@data, 3)
  expect_equal(dim(as.matrix(fit_b@bergm$Theta)), c(8000, 4))
  expect_silent(g <- btergm::gof(fit_b, verbose = FALSE))
  expect_s3_class(g, "gof")
  expect_length(g, 7)
  expect_equivalent(nobs(fit_b), c(10, 900))
  expect_equivalent(coef(fit) - apply(fit_b@bergm$Theta, 2, mean)[-4], rep(0, 3), tolerance = 0.2)
})

test_that("bipartite network objects work, including composition change, with and without offset", {
  skip_on_cran()
  skip_if_not_installed("fastglm", minimum_version = "0.0.1")
  set.seed(12345)
  nw_matrix <- lapply(1:20, function(x) {
    mat <- matrix(rbinom(200, 1, 0.2), nrow = 20)
    rownames(mat) <- letters[1:20]
    colnames(mat) <- letters[1:10]
    mat <- mat[sample(1:20, 18), sample(1:10, 9)]
  })
  nw_network <- lapply(nw_matrix, function(x) {
    network::network(x, bipartite = TRUE, directed = FALSE)
  })
  expect_warning(model1 <- btergm(nw_matrix ~ edges + threetrail + kstar(2) + memory("autoregression"), R = 50, offset = FALSE, verbose = FALSE, usefastglm = TRUE), regexp = NA)
  expect_warning(model2 <- btergm(nw_network ~ edges + threetrail + kstar(2) + memory("autoregression"), R = 50, offset = FALSE, verbose = FALSE, usefastglm = TRUE), regexp = NA)
  expect_warning(model3 <- btergm(nw_matrix ~ edges + threetrail + kstar(2) + memory("autoregression"), R = 50, offset = TRUE, verbose = FALSE, usefastglm = TRUE), regexp = NA)
  expect_warning(model4 <- btergm(nw_network ~ edges + threetrail + kstar(2) + memory("autoregression"), R = 50, offset = TRUE, verbose = FALSE, usefastglm = TRUE), regexp = NA)
  expect_length(model1@coef, 4)
  expect_length(model2@coef, 4)
  expect_length(model3@coef, 4)
  expect_length(model4@coef, 4)
  expect_equal(dim(confint(model1)), c(4, 4))
  expect_equal(dim(confint(model2)), c(4, 4))
  expect_equal(dim(confint(model3)), c(4, 4))
  expect_equal(dim(confint(model4)), c(4, 4))
})

Try the btergm package in your browser

Any scripts or data that you put into this service are public.

btergm documentation built on Oct. 6, 2023, 1:07 a.m.