Nothing
test_that("family_spec normalizes ordinary single-family fits", {
dat <- data.frame(y = c(1.2, 2.4, 3.6))
spec <- .compile_family_spec(gaussian(), data = dat)
expect_identical(spec$n_f, 1L)
expect_identical(spec$n_m, 1L)
expect_identical(spec$distribution_column, NULL)
expect_equal(spec$family_id_i, rep(1L, nrow(dat)))
expect_equal(spec$families$combine_kind, "single")
expect_equal(spec$components$family_code, unname(.valid_family["gaussian"]))
expect_equal(spec$components$link_code, unname(.valid_link["identity"]))
expect_equal(spec$param_slot$ln_phi, 1L)
expect_true(is.na(spec$param_slot$thetaf))
y_out <- .family_spec_build_response(dat$y, spec)
expect_identical(ncol(y_out), 1L)
expect_equal(y_out[, 1], dat$y)
})
test_that("family_spec normalizes mixed family metadata", {
fam <- list(
gauss = gaussian(),
delta = delta_gamma(type = "poisson-link"),
tw = tweedie()
)
dat <- data.frame(
y = c(1.2, 0, 2.4, 3.1, 0.8),
dist = c("gauss", "delta", "delta", "tw", "gauss")
)
spec <- .compile_family_spec(
family = fam,
data = dat,
distribution_column = "dist"
)
expect_identical(spec$n_f, 3L)
expect_identical(spec$n_m, 2L)
expect_equal(spec$family_id_i, c(1L, 2L, 2L, 3L, 1L))
expect_equal(spec$families$combine_kind, c("single", "poisson_link_delta", "single"))
expect_equal(
spec$components[, c("family_id", "component", "family_name", "link_name")],
data.frame(
family_id = c(1L, 2L, 2L, 3L), component = c(1L, 1L, 2L, 1L),
family_name = c("gaussian", "binomial", "Gamma", "tweedie"),
link_name = c("identity", "log", "log", "log")
)
)
expect_equal(spec$param_slot$ln_phi, c(1L, 2L, 3L))
expect_equal(spec$param_slot$thetaf, c(NA_integer_, NA_integer_, 1L))
y_out <- .family_spec_build_response(dat$y, spec)
expect_equal(y_out[1, ], c(1.2, NA_real_))
expect_equal(y_out[2, ], c(0, NA_real_))
expect_equal(y_out[3, ], c(1, 2.4))
expect_equal(y_out[4, ], c(3.1, NA_real_))
})
test_that("multi-family fits retain the complete user family input", {
family_input <- list(
count = nbinom2(),
biomass = delta_lognormal(type = "poisson-link")
)
spec <- .compile_family_spec(
family_input,
data = data.frame(y = c(1, 0), dist = c("count", "biomass")),
distribution_column = "dist"
)
object <- structure(
list(family = spec$family_input, family_spec = spec),
class = "sdmTMB"
)
expect_identical(object$family, family_input)
expect_identical(family(object), family_input)
})
test_that("family_spec accepts the supported multi-family scope", {
dat <- data.frame(
y = c(1.2, 0, 2.4, 3.1, 0.5, 4, 1),
dist = c("gauss", "dg", "dl", "nb1", "bb", "tw", "dg")
)
spec <- .compile_family_spec(
family = list(
gauss = gaussian(),
dg = delta_gamma(),
dl = delta_lognormal(type = "poisson-link"),
nb1 = nbinom1(),
bb = betabinomial(),
tw = tweedie()
),
data = dat,
distribution_column = "dist"
)
expect_identical(spec$n_f, 6L)
expect_identical(spec$n_m, 2L)
})
test_that("family_spec rejects families outside the multi-family scope early", {
dat <- data.frame(
y = c(1.2, 3.1, 0.5, 4, 1, 2, 0.4, 2.2, 1),
dist = c("a", "b", "c", "d", "e", "f", "g", "h", "i")
)
rejected <- list(
a = stats::Gamma(link = "log"),
b = lognormal(),
c = student(),
d = censored_poisson(),
e = truncated_nbinom2(),
f = gamma_mix(),
g = delta_gamma_mix(),
h = delta_truncated_nbinom2(),
i = ordbeta()
)
for (nm in names(rejected)) {
expect_error(
.compile_family_spec(
family = list(ok = gaussian(), bad = rejected[[nm]]),
data = dat,
distribution_column = "dist"
),
regexp = "not supported in multi-family mode"
)
}
# ordinary single-family fits keep every family
spec <- .compile_family_spec(student(), data = dat)
expect_identical(spec$n_f, 1L)
})
test_that("family_spec processes rowwise binomial-like responses regardless of family order", {
dat <- data.frame(
y = c(0.2, 1, 0, 1, 0.4, 2, 3.5),
dist = c("binom", "binom", "betabinom", "betabinom", "betabinom", "betabinom", "gauss")
)
weights <- c(10, NA, 5, 5, 12, 7, 1)
run_case <- function(fam) {
spec <- .compile_family_spec(
family = fam,
data = dat,
distribution_column = "dist"
)
.family_spec_process_response(
y_i = dat$y,
size = rep(1, nrow(dat)),
weights = weights,
family_spec = spec
)
}
res <- run_case(list(
binom = binomial(),
betabinom = betabinomial(),
gauss = gaussian()
))
res_reversed <- run_case(list(
gauss = gaussian(),
betabinom = betabinomial(),
binom = binomial()
))
expect_equal(res, res_reversed)
expect_equal(res$y_i[1], 2)
expect_equal(res$size[1], 10)
expect_equal(res$weights[1], 1)
expect_equal(res$y_i[2], 1)
expect_equal(res$size[2], 1)
expect_equal(res$weights[2], 1)
expect_equal(res$y_i[3:6], c(0, 1, 4.8, 2))
expect_equal(res$size[3:6], c(5, 5, 12, 7))
expect_equal(res$weights[3:6], rep(1, 4))
})
test_that("multi-family beta-binomial weighted integer responses reach TMB as counts", {
dat <- data.frame(
y = c(0, 1, 0.25, 2, 3.5),
x = seq(-1, 1, length.out = 5),
dist = c("betabinom", "betabinom", "betabinom", "betabinom", "gauss")
)
weights <- c(4, 5, 8, 7, 1)
fit_a <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian(), betabinom = betabinomial()),
distribution_column = "dist",
weights = weights,
do_fit = FALSE
)
fit_b <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = list(betabinom = betabinomial(), gauss = gaussian()),
distribution_column = "dist",
weights = weights,
do_fit = FALSE
)
bb_rows <- dat$dist == "betabinom"
expect_equal(fit_a$tmb_data$size[bb_rows], c(4, 5, 8, 7))
expect_equal(fit_b$tmb_data$size[bb_rows], c(4, 5, 8, 7))
expect_equal(as.numeric(fit_a$tmb_data$y_i[bb_rows, 1]), c(0, 1, 2, 2))
expect_equal(as.numeric(fit_b$tmb_data$y_i[bb_rows, 1]), c(0, 1, 2, 2))
})
test_that("object_family_spec can rebuild stored single-family fields", {
dat <- data.frame(y = c(1.2, 0, 2.4, 3.1))
obj <- list(
family = list(gauss = gaussian()),
data = dat
)
spec <- .object_family_spec(obj)
expect_identical(spec$n_f, 1L)
expect_identical(spec$n_m, 1L)
expect_identical(spec$distribution_column, NULL)
expect_equal(spec$family_id_i, rep(1L, nrow(dat)))
})
test_that("object_family_spec requires canonical metadata for old multi-family objects", {
dat <- data.frame(
y = c(1.2, 0, 2.4, 3.1),
dist = c("gauss", "delta", "delta", "gauss")
)
obj <- list(
family = list(gauss = gaussian(), delta = delta_gamma()),
data = dat,
distribution_column = "dist"
)
expect_error(
.object_family_spec(obj, caller = "`predict()`"),
regexp = "Refit this model with the current version of sdmTMB"
)
})
test_that("multi-family fits build family-based TMB data", {
dat <- data.frame(
y = c(1.2, 0, 2.4, 3.1),
x = c(-1, -0.3, 0.4, 1),
dist = c("gauss", "delta", "delta", "gauss")
)
fit <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian(), delta = delta_gamma()),
distribution_column = "dist",
do_fit = FALSE
)
expect_equal(fit$tmb_data$obs_family_id, c(0L, 1L, 1L, 0L))
expect_equal(fit$tmb_data$component_active, matrix(c(1L, 0L, 1L, 1L), nrow = 2L, byrow = TRUE))
expect_equal(fit$tmb_data$combine_kind, c(0L, 1L))
expect_equal(fit$tmb_data$ln_phi_slot, c(0L, 1L))
expect_equal(fit$tmb_data$thetaf_slot, c(-1L, -1L))
expect_equal(fit$tmb_data$y_i[1, ], c(1.2, NA_real_))
expect_equal(fit$tmb_data$y_i[2, ], c(0, NA_real_))
expect_equal(fit$tmb_data$y_i[3, ], c(1, 2.4))
expect_equal(fit$tmb_data$y_i[4, ], c(3.1, NA_real_))
expect_length(fit$tmb_params$ln_phi, 2L)
expect_identical(names(fit$family), c("gauss", "delta"))
expect_identical(family(fit), fit$family)
})
test_that("rows with missing covariates are omitted before fitting", {
dat_single <- data.frame(
y = c(1.2, 2.4, 3.6, 4.8, 6.0),
x = c(-1, NA, 0, 0.5, 1)
)
fit <- sdmTMB(
y ~ x,
data = dat_single,
spatial = "off",
spatiotemporal = "off",
family = gaussian(),
do_fit = FALSE
)
expect_equal(fit$data$y, c(1.2, 3.6, 4.8, 6.0))
expect_equal(fit$tmb_data$y_i[, 1], c(1.2, 3.6, 4.8, 6.0))
# prediction data must still be complete:
expect_error(
predict(fit, newdata = dat_single),
regexp = "Rows with missing covariates: 2"
)
# a missing value in any component's formula omits the row:
dat_multi <- data.frame(
y = c(1.2, 0.0, 2.1),
x1 = c(-1, 0, 1),
x2 = c(NA, -0.2, 0.3),
dist = c("gauss", "delta", "delta")
)
fit_multi <- sdmTMB(
formula = list(y ~ x1, y ~ x2),
data = dat_multi,
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian(), delta = delta_gamma()),
distribution_column = "dist",
do_fit = FALSE
)
expect_equal(fit_multi$data$x1, c(0, 1))
expect_equal(fit_multi$family_spec$family_id_i, c(2L, 2L))
})
test_that("omitted rows are dropped from all row-level data", {
dat_single <- data.frame(
y = c(1.2, NA, 3.6, 4.8, 6.0),
x = c(-1, -0.3, 0, 0.5, 1)
)
fit_single <- sdmTMB(
y ~ x,
data = dat_single,
spatial = "off",
spatiotemporal = "off",
family = gaussian(),
offset = c(10, 20, 30, 40, 50),
control = sdmTMBcontrol(getsd = FALSE, newton_loops = 0)
)
expect_equal(fit_single$data$x, c(-1, 0, 0.5, 1))
expect_equal(fit_single$tmb_data$offset_i, c(10, 30, 40, 50))
expect_equal(fit_single$tmb_data$obs_family_id, rep(0L, 4L))
expect_equal(fit_single$family_spec$family_id_i, rep(1L, 4L))
expect_equal(fit_single$tmb_data$y_i[, 1], c(1.2, 3.6, 4.8, 6.0))
expect_equal(fit_single$offset, c(10, 30, 40, 50))
expect_equal(nobs(fit_single), 4L)
expect_length(fitted(fit_single), 4L)
expect_equal(
fitted(fit_single),
predict(fit_single, type = "response")$est,
tolerance = 1e-6
)
dat_multi <- data.frame(
y = c(1.2, NA, 2.1, 0.0, 3.5),
x = c(-1, -0.3, -0.1, 0.4, 1),
dist = c("gauss", "delta", "delta", "gauss", "delta")
)
fit_multi <- sdmTMB(
y ~ x,
data = dat_multi,
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian(), delta = delta_gamma()),
distribution_column = "dist",
offset = c(5, 6, 7, 8, 9),
do_fit = FALSE
)
expect_equal(fit_multi$data$x, c(-1, -0.1, 0.4, 1))
expect_equal(fit_multi$tmb_data$offset_i, c(5, 7, 8, 9))
expect_equal(fit_multi$tmb_data$obs_family_id, c(0L, 1L, 0L, 1L))
expect_equal(fit_multi$family_spec$family_id_i, c(1L, 2L, 1L, 2L))
expect_equal(fit_multi$tmb_data$y_i[1, ], c(1.2, NA_real_))
expect_equal(fit_multi$tmb_data$y_i[2, ], c(1.0, 2.1))
expect_equal(fit_multi$tmb_data$y_i[3, ], c(0.0, NA_real_))
expect_equal(fit_multi$tmb_data$y_i[4, ], c(1.0, 3.5))
fit_multi_reversed <- sdmTMB(
y ~ x,
data = dat_multi,
spatial = "off",
spatiotemporal = "off",
family = list(delta = delta_gamma(), gauss = gaussian()),
distribution_column = "dist",
offset = c(5, 6, 7, 8, 9),
do_fit = FALSE
)
expect_equal(fit_multi_reversed$tmb_data$offset_i, c(5, 7, 8, 9))
expect_equal(fit_multi_reversed$tmb_data$obs_family_id, c(1L, 0L, 1L, 0L))
expect_equal(fit_multi_reversed$family_spec$family_id_i, c(2L, 1L, 2L, 1L))
expect_equal(fit_multi_reversed$tmb_data$y_i, fit_multi$tmb_data$y_i)
dat_binomial <- data.frame(
y = c(0.2, NA, 0.4),
x = c(-1, 0, 1)
)
fit_binomial <- sdmTMB(
y ~ x,
data = dat_binomial,
spatial = "off",
spatiotemporal = "off",
family = binomial(),
weights = c(10, 20, 30),
do_fit = FALSE
)
expect_equal(as.numeric(fit_binomial$tmb_data$y_i), c(2, 12))
expect_equal(fit_binomial$tmb_data$size, c(10, 30))
})
test_that("response omissions keep spatial indices within the retained rows", {
set.seed(1)
d <- data.frame(X = runif(30), Y = runif(30), x = rnorm(30),
time = rep(1:3, each = 10))
d$y <- 1 + d$x + rnorm(30)
d$y[c(4, 30)] <- NA
mesh <- make_mesh(d, c("X", "Y"), cutoff = 0.1)
fit <- sdmTMB(y ~ x, data = d, mesh = mesh, time = "time",
spatiotemporal = "iid", do_fit = FALSE)
expect_equal(nrow(fit$tmb_data$A_st), 28L)
expect_equal(fit$tmb_data$A_spatial_index, 0:27)
expect_true(is.finite(fit$tmb_obj$fn()))
})
test_that("NA rows give the same fit and post-fit results as dropping them first", {
skip_on_cran()
set.seed(1)
d <- data.frame(X = runif(150), Y = runif(150), x = rnorm(150))
mesh <- make_mesh(d, c("X", "Y"), cutoff = 0.1)
d$y <- sdmTMB_simulate(~ x, data = d, mesh = mesh, range = 0.5,
sigma_O = 0.5, phi = 0.2, B = c(1, 0.5), seed = 1)$observed
d$y[4] <- NA
d$x[30] <- NA
fit <- sdmTMB(y ~ x, data = d, mesh = mesh)
d2 <- d[-c(4, 30), ]
fit2 <- sdmTMB(y ~ x, data = d2,
mesh = make_mesh(d2, c("X", "Y"), mesh = mesh$mesh))
expect_equal(logLik(fit), logLik(fit2))
expect_equal(nobs(fit), 148L)
expect_equal(predict(fit)$est, predict(fit2)$est)
expect_equal(residuals(fit, type = "deviance"), residuals(fit2, type = "deviance"))
expect_equal(nrow(simulate(fit, nsim = 1)), 148L)
set.seed(2)
cv <- sdmTMB_cv(y ~ x, data = d, mesh = mesh, k_folds = 2)
expect_equal(nrow(cv$data), 148L)
expect_true(is.finite(cv$sum_loglik))
})
test_that("mixed gaussian plus delta fits reach the unified TMB path", {
set.seed(13)
x <- seq(-1, 1, length.out = 90)
dist <- rep(c("gauss", "delta", "delta"), length.out = length(x))
gauss_rows <- dist == "gauss"
delta_rows <- !gauss_rows
y <- numeric(length(x))
y[gauss_rows] <- 1 + 0.4 * x[gauss_rows] + stats::rnorm(sum(gauss_rows), sd = 0.15)
present <- stats::rbinom(sum(delta_rows), size = 1, prob = stats::plogis(-0.3 + 0.7 * x[delta_rows]))
y_pos <- stats::rgamma(sum(delta_rows), shape = 6, scale = exp(0.2 + 0.3 * x[delta_rows]) / 6)
y[delta_rows] <- ifelse(present == 1, y_pos, 0)
dat <- data.frame(y = y, x = x, dist = dist)
ctrl <- sdmTMBcontrol(newton_loops = 1, getsd = FALSE)
fit <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian(), delta = delta_gamma()),
distribution_column = "dist",
control = ctrl
)
expect_s3_class(fit, "sdmTMB")
expect_identical(fit$family_spec$n_f, 2L)
expect_identical(fit$family_spec$n_m, 2L)
expect_equal(fit$model$convergence, 0L)
expect_true(is.finite(as.numeric(logLik(fit))))
expect_error(residuals(fit), regexp = "simulation-based residual workflow")
})
.mixed_family_step4_fixture <- function() {
set.seed(16)
x <- seq(-1, 1, length.out = 90)
dist <- rep(c("gauss", "delta", "delta"), length.out = length(x))
gauss_rows <- dist == "gauss"
delta_rows <- !gauss_rows
y <- numeric(length(x))
y[gauss_rows] <- 1 + 0.4 * x[gauss_rows] + stats::rnorm(sum(gauss_rows), sd = 0.15)
present <- stats::rbinom(sum(delta_rows), size = 1, prob = stats::plogis(-0.3 + 0.7 * x[delta_rows]))
y_pos <- stats::rgamma(sum(delta_rows), shape = 6, scale = exp(0.2 + 0.3 * x[delta_rows]) / 6)
y[delta_rows] <- ifelse(present == 1, y_pos, 0)
dat <- data.frame(y = y, x = x, dist = dist, year = 1L)
fit <- sdmTMB(
y ~ x,
data = dat,
time = "year",
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian(), delta = delta_gamma()),
distribution_column = "dist",
control = sdmTMBcontrol(newton_loops = 0, getsd = TRUE)
)
nd <- data.frame(
x = c(-0.8, -0.3, 0.2, 0.6, 0.9),
dist = c("gauss", "delta", "gauss", "delta", "delta"),
year = 1L
)
list(fit = fit, newdata = nd)
}
.mixed_family_standard_delta_fixture <- function() {
set.seed(103)
n_each <- 120L
n <- n_each * 3L
x <- runif(n, -1, 1)
area <- rep(1, n)
dist <- factor(
rep(c("binomial", "poisson", "delta"), each = n_each),
levels = c("binomial", "poisson", "delta")
)
y <- numeric(length(dist))
i_bin <- dist == "binomial"
i_poi <- dist == "poisson"
i_del <- dist == "delta"
y[i_bin] <- stats::rbinom(sum(i_bin), 1, stats::plogis(-0.4 + 0.6 * x[i_bin]))
y[i_poi] <- stats::rpois(sum(i_poi), exp(0.3 + 0.5 * x[i_poi]))
present <- stats::rbinom(sum(i_del), 1, stats::plogis(-0.2 + 0.3 * x[i_del]))
y_pos <- stats::rgamma(sum(i_del), shape = 4, scale = exp(0.4 + 0.5 * x[i_del]) / 4)
y[i_del] <- ifelse(present == 1, y_pos, 0)
dat <- data.frame(
y = y,
x = x,
y_coord = 0,
year = 1L,
dist = dist,
area_swept = area
)
ctrl <- sdmTMBcontrol(multiphase = FALSE, newton_loops = 0, getsd = FALSE)
fit_mix <- sdmTMB(
y ~ 0 + dist + dist:x,
data = dat,
family = list(
binomial = binomial(link = "logit"),
poisson = poisson(link = "log"),
delta = delta_gamma()
),
distribution_column = "dist",
spatial = "off",
spatiotemporal = "off",
weights = rep(1, nrow(dat)),
offset = log(dat$area_swept),
control = ctrl
)
fit_bin <- sdmTMB(
y ~ x,
data = subset(dat, dist == "binomial"),
family = binomial(link = "logit"),
spatial = "off",
spatiotemporal = "off",
weights = rep(1, sum(dat$dist == "binomial")),
control = ctrl
)
fit_poi <- sdmTMB(
y ~ x,
data = subset(dat, dist == "poisson"),
family = poisson(link = "log"),
spatial = "off",
spatiotemporal = "off",
offset = log(subset(dat, dist == "poisson")$area_swept),
control = ctrl
)
fit_del <- sdmTMB(
y ~ x,
data = subset(dat, dist == "delta"),
family = delta_gamma(),
spatial = "off",
spatiotemporal = "off",
offset = log(subset(dat, dist == "delta")$area_swept),
control = ctrl
)
make_newdata <- function(dist_label, area_swept = rep(1, 9L)) {
data.frame(
x = seq(-0.9, 0.9, length.out = 9),
y_coord = 0,
year = 1L,
dist = factor(dist_label, levels = levels(dat$dist)),
area_swept = area_swept
)
}
list(
fit_mix = fit_mix,
fit_bin = fit_bin,
fit_poi = fit_poi,
fit_del = fit_del,
nd_bin = make_newdata("binomial"),
nd_poi = make_newdata("poisson"),
nd_del = make_newdata("delta")
)
}
test_that("mixed-family predict returns rowwise combined and masked component outputs", {
fixture <- .mixed_family_step4_fixture()
gauss_rows <- fixture$newdata$dist == "gauss"
delta_rows <- !gauss_rows
pred_link <- predict(fixture$fit, newdata = fixture$newdata, type = "link")
expect_equal(pred_link$est[gauss_rows], pred_link$est1[gauss_rows], tolerance = 1e-6)
expect_true(all(is.na(pred_link$est2[gauss_rows])))
expect_true(all(is.finite(pred_link$est2[delta_rows])))
expect_equal(
pred_link$est[delta_rows],
log(stats::plogis(pred_link$est1[delta_rows]) * exp(pred_link$est2[delta_rows])),
tolerance = 1e-6
)
pred_lp1 <- predict(fixture$fit, newdata = fixture$newdata, type = "link", model = 1)
pred_lp2 <- predict(fixture$fit, newdata = fixture$newdata, type = "link", model = 2)
expect_equal(pred_lp1$est, pred_lp1$est1, tolerance = 1e-6)
expect_equal(pred_lp2$est, pred_lp2$est2, tolerance = 1e-6)
expect_true(all(is.na(pred_lp2$est[gauss_rows])))
pred_resp <- predict(fixture$fit, newdata = fixture$newdata, type = "response")
expect_equal(pred_resp$est[gauss_rows], pred_resp$est1[gauss_rows], tolerance = 1e-6)
expect_equal(
pred_resp$est[delta_rows],
pred_resp$est1[delta_rows] * pred_resp$est2[delta_rows],
tolerance = 1e-6
)
pred_se <- predict(fixture$fit, newdata = fixture$newdata, type = "link", se_fit = TRUE)
expect_true(all(c("est", "est1", "est2", "est_se") %in% names(pred_se)))
expect_equal(pred_se$est[gauss_rows], pred_se$est1[gauss_rows], tolerance = 1e-6)
expect_error(
predict(fixture$fit, newdata = fixture$newdata, type = "response", se_fit = TRUE),
regexp = "link scale"
)
})
test_that("mixed-family standard delta rows match standalone sdmTMB references", {
fixture <- .mixed_family_standard_delta_fixture()
nd_single <- subset(fixture$nd_del, select = c(x, y_coord, year, area_swept))
pred_mix_bin <- predict(
fixture$fit_mix,
newdata = fixture$nd_bin,
type = "response",
offset = rep(0, nrow(fixture$nd_bin))
)
pred_bin <- predict(fixture$fit_bin, newdata = nd_single, type = "response")
pred_mix_poi <- predict(
fixture$fit_mix,
newdata = fixture$nd_poi,
type = "response",
offset = rep(0, nrow(fixture$nd_poi))
)
pred_poi <- predict(
fixture$fit_poi,
newdata = nd_single,
type = "response",
offset = rep(0, nrow(nd_single))
)
pred_mix_del <- predict(
fixture$fit_mix,
newdata = fixture$nd_del,
type = "response",
offset = rep(0, nrow(fixture$nd_del))
)
pred_del <- predict(
fixture$fit_del,
newdata = nd_single,
type = "response",
offset = rep(0, nrow(nd_single))
)
pred_mix_del1 <- predict(
fixture$fit_mix,
newdata = fixture$nd_del,
type = "response",
model = 1,
offset = rep(0, nrow(fixture$nd_del))
)
pred_del1 <- predict(
fixture$fit_del,
newdata = nd_single,
type = "response",
model = 1,
offset = rep(0, nrow(nd_single))
)
pred_mix_del2 <- predict(
fixture$fit_mix,
newdata = fixture$nd_del,
type = "response",
model = 2,
offset = rep(0, nrow(fixture$nd_del))
)
pred_del2 <- predict(
fixture$fit_del,
newdata = nd_single,
type = "response",
model = 2,
offset = rep(0, nrow(nd_single))
)
expect_equal(pred_mix_bin$est, pred_bin$est, tolerance = 1e-5)
expect_equal(pred_mix_poi$est, pred_poi$est, tolerance = 1e-5)
expect_equal(pred_mix_del$est, pred_del$est, tolerance = 1e-5)
expect_equal(pred_mix_del1$est, pred_del1$est, tolerance = 1e-5)
expect_equal(pred_mix_del2$est, pred_del2$est, tolerance = 1e-5)
})
test_that("mixed-family standard delta offsets only affect the positive component", {
fixture <- .mixed_family_standard_delta_fixture()
nd_off <- fixture$nd_del
nd_off$area_swept <- exp(seq(log(0.7), log(1.3), length.out = nrow(nd_off)))
off <- log(nd_off$area_swept)
nd_single_off <- subset(nd_off, select = c(x, y_coord, year, area_swept))
pred_mix_del1 <- predict(
fixture$fit_mix,
newdata = fixture$nd_del,
type = "response",
model = 1,
offset = rep(0, nrow(fixture$nd_del))
)
pred_mix_del1_off <- predict(
fixture$fit_mix,
newdata = nd_off,
type = "response",
model = 1,
offset = off
)
pred_mix_del2_off <- predict(
fixture$fit_mix,
newdata = nd_off,
type = "response",
model = 2,
offset = off
)
pred_mix_del_off <- predict(
fixture$fit_mix,
newdata = nd_off,
type = "response",
offset = off
)
pred_del1_off <- predict(
fixture$fit_del,
newdata = nd_single_off,
type = "response",
model = 1,
offset = off
)
pred_del2_off <- predict(
fixture$fit_del,
newdata = nd_single_off,
type = "response",
model = 2,
offset = off
)
pred_del_off <- predict(
fixture$fit_del,
newdata = nd_single_off,
type = "response",
offset = off
)
expect_equal(pred_mix_del1_off$est, pred_mix_del1$est, tolerance = 1e-8)
expect_equal(pred_mix_del1_off$est, pred_del1_off$est, tolerance = 1e-5)
expect_equal(pred_mix_del2_off$est, pred_del2_off$est, tolerance = 1e-5)
expect_equal(pred_mix_del_off$est, pred_del_off$est, tolerance = 1e-5)
})
test_that("mixed-family simulate combines rows the same way as predict", {
fixture <- .mixed_family_step4_fixture()
gauss_rows <- fixture$newdata$dist == "gauss"
delta_rows <- !gauss_rows
sim_combined <- simulate(
fixture$fit,
nsim = 2,
seed = 101,
newdata = fixture$newdata,
model = NA,
type = "mle-eb",
silent = TRUE
)
sim_lp1 <- simulate(
fixture$fit,
nsim = 2,
seed = 101,
newdata = fixture$newdata,
model = 1,
type = "mle-eb",
silent = TRUE
)
sim_lp2 <- simulate(
fixture$fit,
nsim = 2,
seed = 101,
newdata = fixture$newdata,
model = 2,
type = "mle-eb",
silent = TRUE
)
expect_equal(sim_combined[gauss_rows, , drop = FALSE], sim_lp1[gauss_rows, , drop = FALSE], tolerance = 1e-10)
expect_true(all(is.na(sim_lp2[gauss_rows, , drop = FALSE])))
expect_equal(
sim_combined[delta_rows, , drop = FALSE],
sim_lp1[delta_rows, , drop = FALSE] * sim_lp2[delta_rows, , drop = FALSE],
tolerance = 1e-10
)
expect_error(project(fixture$fit, newdata = fixture$newdata, nsim = 1), regexp = "not yet supported")
})
test_that("mixed-family weighted-average and EAO helpers use rowwise families", {
fixture <- .mixed_family_step4_fixture()
area <- c(1, 2, 1, 3, 2)
pred_resp <- predict(fixture$fit, newdata = fixture$newdata, type = "response")
lifecycle::expect_deprecated(
pred_obj <- predict(fixture$fit, newdata = fixture$newdata, return_tmb_object = TRUE),
"return_tmb_object"
)
wa <- get_weighted_average(
pred_obj,
vector = fixture$newdata$x,
area = area,
bias_correct = FALSE
)
manual_total <- sum(pred_resp$est * area)
manual_wa <- sum(pred_resp$est * fixture$newdata$x * area) / manual_total
expect_equal(wa$est, manual_wa, tolerance = 1e-6)
eao <- get_eao(
pred_obj,
area = area,
bias_correct = FALSE
)
manual_mean_dens <- sum(area * pred_resp$est^2) / manual_total
manual_eao <- manual_total / manual_mean_dens
expect_equal(eao$est, manual_eao, tolerance = 1e-6)
})
test_that("mixed-family tidy and print report component and family summaries", {
fixture <- .mixed_family_step4_fixture()
tidy_lp2 <- tidy(fixture$fit, model = 2)
expect_true(all(c("term", "estimate", "std.error") %in% names(tidy_lp2)))
tidy_ran <- tidy(fixture$fit, effects = "ran_pars", model = 1)
expect_true(any(tidy_ran$group_name == "gauss" & tidy_ran$term == "phi"))
expect_true(any(tidy_ran$group_name == "delta" & tidy_ran$term == "phi"))
printed <- paste(capture.output(print(fixture$fit)), collapse = "\n")
expect_match(printed, "Linear predictor 1", fixed = TRUE)
expect_match(printed, "Families:", fixed = TRUE)
expect_match(printed, "distribution column = 'dist'", fixed = TRUE)
})
test_that("mixed-family methods use family-spec routing and guard unsupported summaries", {
fixture <- .mixed_family_step4_fixture()
fit <- fixture$fit
pred_resp <- predict(fit, newdata = fit$data, type = "response")
expect_equal(fitted(fit), pred_resp$est, tolerance = 1e-6)
fam <- family(fit)
expect_true(is.list(fam))
expect_identical(names(fam), c("gauss", "delta"))
expect_identical(fit$family, fam)
vc_lp2 <- vcov(fit, model = 2)
expect_identical(colnames(vc_lp2), rownames(vc_lp2))
expect_equal(colnames(vc_lp2), tidy(fit, model = 2)$term)
expect_error(sigma(fit), regexp = "not yet supported")
expect_error(deviance(fit), regexp = "not yet supported")
expect_error(spread_sims(fit), regexp = "not yet supported")
expect_error(gather_sims(fit), regexp = "not yet supported")
})
test_that("mixed-family unsupported methods fail early behind one guard", {
fixture <- .mixed_family_step4_fixture()
fit <- fixture$fit
expect_error(residuals(fit), regexp = "not yet supported")
expect_error(plot_smooth(fit), regexp = "not yet supported")
expect_error(visreg_delta(fit), regexp = "not yet supported")
expect_error(visreg2d_delta(fit), regexp = "not yet supported")
expect_error(cAIC(fit), regexp = "not yet supported")
skip_if_not_installed("emmeans")
expect_error(emmeans::emmeans(fit, ~x), regexp = "not yet supported")
skip_if_not_installed("effects")
expect_error(effects::Effect("x", fit), regexp = "not yet supported")
})
test_that("mixed-family get_index works on a single-family grid via the fit object", {
fixture <- .mixed_family_step4_fixture()
nd_delta <- fixture$newdata
nd_delta$dist <- "delta"
pred_delta <- predict(fixture$fit, newdata = nd_delta, type = "response")
ind <- get_index(
fixture$fit,
newdata = nd_delta,
area = 2,
bias_correct = FALSE
)
expect_s3_class(ind, "data.frame")
expect_equal(ind$est, sum(pred_delta$est * 2), tolerance = 1e-6)
})
test_that("multi-family sdmTMB_cv guard fails before any fitting", {
dat <- data.frame(
y = c(1.2, 0, 2.4, 3.1),
x = c(-1, -0.3, 0.4, 1),
dist = c("gauss", "delta", "delta", "gauss")
)
expect_error(
sdmTMB_cv(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian(), delta = delta_gamma()),
distribution_column = "dist",
k_folds = 2
),
regexp = "not yet supported"
)
})
test_that("multi-family do_index guard fails before fit setup continues", {
dat <- data.frame(
y = c(1.2, 0, 2.4, 3.1),
x = c(-1, -0.3, 0.4, 1),
dist = c("gauss", "delta", "delta", "gauss")
)
expect_error(
sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian(), delta = delta_gamma()),
distribution_column = "dist",
do_index = TRUE,
do_fit = FALSE,
predict_args = list(newdata = dat),
index_args = list(area = 1)
),
regexp = "not yet supported"
)
})
test_that("named single-family list gaussian matches ordinary gaussian fit", {
set.seed(11)
x <- seq(-1, 1, length.out = 40)
dat <- data.frame(
y = 0.4 + 0.7 * x + stats::rnorm(length(x), sd = 0.15),
x = x
)
ctrl <- sdmTMBcontrol(newton_loops = 0, getsd = FALSE)
fit_regular <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = gaussian(),
control = ctrl
)
fit_list <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = list(gauss = gaussian()),
control = ctrl
)
expect_identical(fit_regular$family_spec$n_f, 1L)
expect_identical(fit_list$family_spec$n_f, 1L)
expect_equal(as.numeric(logLik(fit_list)), as.numeric(logLik(fit_regular)), tolerance = 1e-6)
nd <- data.frame(x = seq(-0.8, 0.8, length.out = 12))
expect_equal(
predict(fit_list, newdata = nd, type = "link")$est,
predict(fit_regular, newdata = nd, type = "link")$est,
tolerance = 1e-6
)
expect_equal(
predict(fit_list, newdata = nd, type = "response")$est,
predict(fit_regular, newdata = nd, type = "response")$est,
tolerance = 1e-6
)
})
test_that("named single-family list delta matches ordinary delta predictions", {
set.seed(12)
x <- seq(-1, 1, length.out = 80)
eta1 <- -0.2 + 0.6 * x
eta2 <- 0.3 + 0.4 * x
present <- stats::rbinom(length(x), size = 1, prob = stats::plogis(eta1))
y_pos <- stats::rgamma(length(x), shape = 8, scale = exp(eta2) / 8)
dat <- data.frame(
y = ifelse(present == 1, y_pos, 0),
x = x
)
ctrl <- sdmTMBcontrol(newton_loops = 0, getsd = FALSE)
fit_regular <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = delta_gamma(),
control = ctrl
)
fit_list <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = list(delta = delta_gamma()),
control = ctrl
)
expect_identical(fit_list$family_spec$n_m, 2L)
nd <- data.frame(x = seq(-0.8, 0.8, length.out = 12))
for (type in c("link", "response")) {
pred_regular <- predict(fit_regular, newdata = nd, type = type, model = NA)
pred_list <- predict(fit_list, newdata = nd, type = type, model = NA)
expect_equal(pred_list$est, pred_regular$est, tolerance = 1e-5)
expect_equal(pred_list$est1, pred_regular$est1, tolerance = 1e-5)
expect_equal(pred_list$est2, pred_regular$est2, tolerance = 1e-5)
}
})
test_that("delta sdreport exposes generic combined prediction names", {
set.seed(14)
x <- seq(-1, 1, length.out = 60)
eta1 <- -0.1 + 0.5 * x
eta2 <- 0.2 + 0.3 * x
present <- stats::rbinom(length(x), size = 1, prob = stats::plogis(eta1))
y_pos <- stats::rgamma(length(x), shape = 7, scale = exp(eta2) / 7)
dat <- data.frame(
y = ifelse(present == 1, y_pos, 0),
x = x
)
ctrl <- sdmTMBcontrol(newton_loops = 0, getsd = FALSE)
fit <- sdmTMB(
y ~ x,
data = dat,
spatial = "off",
spatiotemporal = "off",
family = delta_gamma(),
control = ctrl
)
nd <- data.frame(x = seq(-0.8, 0.8, length.out = 10))
lifecycle::expect_deprecated(
pred_obj <- predict(fit, newdata = nd, se_fit = TRUE, return_tmb_object = TRUE),
"return_tmb_object"
)
pred_sr <- sdreport_sdmTMB(pred_obj$obj, bias.correct = FALSE)
pred_rep <- as.list(pred_sr, "Estimate", report = TRUE)
expect_true("proj_eta_combined" %in% names(pred_rep))
expect_false("proj_eta_delta" %in% names(pred_rep))
report <- predict(fit, newdata = nd, return_tmb_report = TRUE)
expect_true("proj_response_combined" %in% names(report))
expect_equal(
predict(fit, newdata = nd, type = "link")$est,
as.numeric(report$proj_eta_combined)
)
expect_equal(
predict(fit, newdata = nd, type = "response")$est,
as.numeric(report$proj_response_combined)
)
lifecycle::expect_deprecated(
pop_pred_obj <- predict(
fit,
newdata = nd,
se_fit = TRUE,
re_form = NA,
return_tmb_object = TRUE
),
"return_tmb_object"
)
pop_pred_sr <- sdreport_sdmTMB(pop_pred_obj$obj, bias.correct = FALSE)
pop_pred_rep <- as.list(pop_pred_sr, "Estimate", report = TRUE)
expect_true("proj_fe_combined" %in% names(pop_pred_rep))
expect_false("proj_rf_delta" %in% names(pop_pred_rep))
})
.poisson_link_delta_step3_fixture <- function() {
set.seed(15)
x <- seq(-1, 1, length.out = 120)
eta1 <- -0.7 + 0.6 * x
eta2 <- 0.2 + 0.3 * x
p <- 1 - exp(-exp(eta1))
mu_pos <- exp(eta1 + eta2) / p
present <- stats::rbinom(length(x), size = 1, prob = p)
y_pos <- stats::rgamma(length(x), shape = 6, scale = mu_pos / 6)
dat <- data.frame(
y = ifelse(present == 1, y_pos, 0),
x = x,
year = 1L
)
ctrl <- sdmTMBcontrol(newton_loops = 1, getsd = FALSE)
fit <- sdmTMB(
y ~ x,
data = dat,
time = "year",
spatial = "off",
spatiotemporal = "off",
family = delta_gamma(type = "poisson-link"),
control = ctrl
)
list(fit = fit, data = dat)
}
test_that("poisson-link delta ordinary se_fit uses generic combined report", {
fixture <- .poisson_link_delta_step3_fixture()
nd <- data.frame(
x = seq(-0.8, 0.8, length.out = 12),
year = 1L
)
pred <- predict(
fixture$fit,
newdata = nd,
type = "link",
se_fit = TRUE
)
expect_equal(as.numeric(pred$est), as.numeric(pred$est1 + pred$est2), tolerance = 1e-6)
lifecycle::expect_deprecated(
pred_obj <- predict(
fixture$fit,
newdata = nd,
type = "link",
se_fit = TRUE,
return_tmb_object = TRUE
),
"return_tmb_object"
)
pred_sr <- sdreport_sdmTMB(pred_obj$obj, bias.correct = FALSE)
pred_rep <- as.list(pred_sr, "Estimate", report = TRUE)
expect_true("proj_eta_combined" %in% names(pred_rep))
expect_false("proj_eta_delta" %in% names(pred_rep))
report <- predict(fixture$fit, newdata = nd, return_tmb_report = TRUE)
expect_equal(
predict(fixture$fit, newdata = nd, type = "response")$est,
as.numeric(report$proj_response_combined)
)
})
test_that("poisson-link delta population se_fit uses generic combined report", {
fixture <- .poisson_link_delta_step3_fixture()
nd <- data.frame(
x = seq(-0.8, 0.8, length.out = 12),
year = 1L
)
pred <- predict(
fixture$fit,
newdata = nd,
type = "link",
se_fit = TRUE,
re_form = NA
)
expect_equal(as.numeric(pred$est), as.numeric(pred$est1 + pred$est2), tolerance = 1e-6)
lifecycle::expect_deprecated(
pred_obj <- predict(
fixture$fit,
newdata = nd,
type = "link",
se_fit = TRUE,
re_form = NA,
return_tmb_object = TRUE
),
"return_tmb_object"
)
pred_sr <- sdreport_sdmTMB(pred_obj$obj, bias.correct = FALSE)
pred_rep <- as.list(pred_sr, "Estimate", report = TRUE)
expect_true("proj_fe_combined" %in% names(pred_rep))
expect_false("proj_rf_delta" %in% names(pred_rep))
})
test_that("poisson-link delta get_index matches constant-grid response prediction", {
fixture <- .poisson_link_delta_step3_fixture()
nd <- data.frame(
x = rep(0, 40),
year = 1L
)
lifecycle::expect_deprecated(
pred_obj <- predict(
fixture$fit,
newdata = nd,
return_tmb_object = TRUE
),
"return_tmb_object"
)
ind <- get_index(
pred_obj,
area = rep(1 / nrow(nd), nrow(nd)),
bias_correct = FALSE
)
pred_response <- predict(
fixture$fit,
newdata = nd[1, , drop = FALSE],
type = "response"
)
expect_equal(ind$est, pred_response$est[1], tolerance = 1e-3)
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.