tests/testthat/test-dcee-summary.R

test_that("summary.dcee: single vector lincomb matches manual L %*% beta", {
    skip_on_cran()

    dat <- data_distal_continuous

    # Need at least 2 betas -> include one moderator (Z)
    fit <- dcee(
        data = dat,
        id = "userid", outcome = "Y", treatment = "A", rand_prob = "prob_A",
        moderator_formula = ~Z,
        control_formula = ~ X + Z,
        availability = "avail",
        control_reg_method = "lm",
        cross_fit = FALSE,
        verbose = FALSE
    )

    s <- summary(fit, lincomb = c(0, 1), conf_level = 0.95)

    beta <- fit$fit$beta_hat
    V <- fit$fit$beta_varcov
    L <- matrix(c(0, 1), nrow = 1)
    est <- as.numeric(L %*% beta)
    se <- sqrt(as.numeric(L %*% V %*% t(L)))
    df <- fit$df
    tcrit <- stats::qt(0.975, df = df)

    # pull from summary table
    got <- s$lincomb[1, , drop = FALSE]

    expect_equal(got$Estimate, est, tolerance = 1e-8)
    expect_equal(got$`Std. Error`, se, tolerance = 1e-8)
    expect_equal(got$df, df)
    expect_equal(got$`t value`, est / se, tolerance = 1e-8)
    expect_equal(got[[grep("LCL$", names(got), value = TRUE)]], est - tcrit * se, tolerance = 1e-8)
    expect_equal(got[[grep("UCL$", names(got), value = TRUE)]], est + tcrit * se, tolerance = 1e-8)
})



test_that("summary.dcee: matrix lincomb (multiple rows) and rownames preserved", {
    skip_on_cran()

    dat <- data_distal_continuous

    fit <- dcee(
        data = dat,
        id = "userid", outcome = "Y", treatment = "A", rand_prob = "prob_A",
        moderator_formula = ~Z,
        control_formula = ~ X + Z,
        availability = "avail",
        control_reg_method = "lm",
        verbose = FALSE
    )

    # L1 = Intercept only; L2 = Moderator only
    L <- rbind(
        Intercept = c(1, 0),
        Z_only    = c(0, 1)
    )

    s <- summary(fit, lincomb = L, conf_level = 0.90) # non-default CL to exercise tcrit change
    expect_true(is.data.frame(s$lincomb))
    expect_equal(rownames(s$lincomb), rownames(L))

    beta <- fit$fit$beta_hat
    V <- fit$fit$beta_varcov
    est <- as.numeric(L %*% beta)
    se <- sqrt(diag(L %*% V %*% t(L)))
    df <- fit$df
    tcrit <- stats::qt(0.95, df = df) # 90% CI

    expect_equal(as.numeric(s$lincomb$Estimate), as.numeric(est), tolerance = 1e-8)
    expect_equal(as.numeric(s$lincomb$`Std. Error`), as.numeric(se), tolerance = 1e-8)
    expect_equal(s$lincomb$df, rep(df, length(est)))
    expect_equal(as.numeric(s$lincomb$`t value`), as.numeric(est / se), tolerance = 1e-8)

    LCL_col <- grep("LCL$", names(s$lincomb), value = TRUE)
    UCL_col <- grep("UCL$", names(s$lincomb), value = TRUE)
    expect_equal(as.numeric(s$lincomb[[LCL_col]]), as.numeric(est - tcrit * se), tolerance = 1e-8)
    expect_equal(as.numeric(s$lincomb[[UCL_col]]), as.numeric(est + tcrit * se), tolerance = 1e-8)
})




test_that("summary.dcee: transposes lincomb when user supplies p x k", {
    skip_on_cran()

    dat <- data_distal_continuous

    fit <- dcee(
        data = dat,
        id = "userid", outcome = "Y", treatment = "A", rand_prob = "prob_A",
        moderator_formula = ~Z,
        control_formula = ~ X + Z,
        availability = "avail",
        control_reg_method = "lm",
        verbose = FALSE
    )

    p <- length(fit$fit$beta_hat)
    expect_equal(p, 2L) # sanity

    # Provide p x k instead of k x p. Here, p x 2 → will be transposed internally.
    L_wrong_shape <- cbind(c(1, 0), c(0, 1)) # 2x2
    colnames(L_wrong_shape) <- NULL
    rownames(L_wrong_shape) <- NULL

    s <- summary(fit, lincomb = L_wrong_shape, conf_level = 0.95)
    expect_true(is.data.frame(s$lincomb))
    expect_equal(nrow(s$lincomb), 2L)
})


test_that("summary.dcee: invalid lincomb shapes error clearly", {
    skip_on_cran()

    dat <- data_distal_continuous

    fit <- dcee(
        data = dat,
        id = "userid", outcome = "Y", treatment = "A", rand_prob = "prob_A",
        moderator_formula = ~Z,
        control_formula = ~ X + Z,
        availability = "avail",
        control_reg_method = "lm",
        verbose = FALSE
    )

    # vector wrong length
    expect_error(summary(fit, lincomb = c(1, 1, 1)), "`lincomb` vector must have length 2.", ignore.case = TRUE)

    # matrix with wrong cols/rows
    expect_error(summary(fit, lincomb = matrix(1, nrow = 3, ncol = 3)),
        "must have 2 columns",
        fixed = FALSE
    )
})



test_that("summary.dcee: lincomb works with GAM nuisance as well", {
    skip_on_cran()
    skip_if_not_installed("mgcv")

    dat <- data_distal_continuous

    fit <- dcee(
        data = dat,
        id = "userid", outcome = "Y", treatment = "A", rand_prob = "prob_A",
        moderator_formula = ~Z,
        control_formula = ~ s(X) + Z,
        availability = "avail",
        control_reg_method = "gam",
        verbose = FALSE
    )

    s <- summary(fit, lincomb = c(0, 1))
    expect_true(is.data.frame(s$lincomb))
    expect_equal(nrow(s$lincomb), 1L)
    expect_true(all(is.finite(unlist(s$lincomb))))
})

Try the MRTAnalysis package in your browser

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

MRTAnalysis documentation built on Sept. 9, 2025, 5:41 p.m.