tests/testthat/test-pool.R

test_that("Rubin's rules", {

    set.seed(101)
    ests <- rnorm(100)
    ses <- rnorm(100, mean = 10, sd = 1)
    v_com <- seq(1, 1000, by = 100)
    n <- seq(16, 1015, by = 100)
    k <- 15

    actual_res <- sapply(
        v_com, function(i) rubin_rules(ests, ses, i),
        simplify = FALSE
    )

    expect_equal(actual_res, mice_res1)


    # check when no variability in estimates (i.e. when no missing values)
    ests_allequal <- rep(0, 100)

    actual_res <- sapply(
        v_com,
        function(i) rubin_rules(ests_allequal, ses, i),
        simplify = FALSE
    )
    expect_equal(actual_res, mice_res2, tolerance = 10e-4)


    # check when v_com <- Inf
    v_com <- Inf
    actual_res <- rubin_rules(ests, ses, v_com)
    expect_equal(actual_res, mice_res3)


    # when v_com = NA or v_com = Inf and there are no missing values, df = Inf
    v_com <- c(Inf, NA)
    actual_res <- sapply(
        v_com,
        function(i) rubin_rules(ests_allequal, ses, i)$df
    )

    expect_true(all(actual_res == Inf))


    # if ses are all NA, then results are only for point estimate
    ses <- rep(NA, 100)
    v_com <- Inf
    expect_equal(
        rubin_rules(ests, ses, v_com),
        list(
            est_point = mean(ests),
            var_t = NA,
            df = NA
        )
    )


})


test_that("pval_percentile", {

    est <- c(0,rep(1,3))
    pvals <- pval_percentile(est)
    expected <- c("pval_greater" = 0.2, "pval_less" = 0.8)
    expect_equal(pvals, expected)

    est <- rep(0,4)
    pvals <- pval_percentile(est)
    expected <- c("pval_greater" = 1, "pval_less" = 1)
    expect_equal(pvals, expected)

    est <- c(0, rep(-1,3))
    pvals <- pval_percentile(est)
    expected <- c("pval_greater" = 0.8, "pval_less" = 0.2)
    expect_equal(pvals, expected)

    est <- rep(1,4)
    pvals <- pval_percentile(est)
    expected <- c("pval_greater" = 0, "pval_less" = 1)
    expect_equal(pvals, expected)

    est <- rep(-1,4)
    pvals <- pval_percentile(est)
    expected <- c("pval_greater" = 1, "pval_less" = 0)
    expect_equal(pvals, expected)

    set.seed(101)
    est <- rnorm(10000)
    pvals <- pval_percentile(est)
    expect_true(all(pvals > 0.485 & pvals < 0.515)) # the "true" p-values are 0.5
})

test_that("get_ests_bmlmi", {

    ests <- rnorm(100)
    D <- 5

    res <- get_ests_bmlmi(ests, D)

    expect_true(is.list(res))
    expect_length(res, 3)
    expect_true(all(!is.na(res)))
    expect_true(all(!is.null(res)))
    expect_true(all(sapply(res, is.numeric)))
    expect_true(all(sapply(res, function(x) length(x) == 1)))
    expect_equal(round(res$est_point, 4), round(mean(ests), 4))

    D <- 3
    expect_error(
        get_ests_bmlmi(ests, D),
        regexp = "length of `ests` must be a multiple of `D`"
    )

    D <- 1
    expect_error(
        get_ests_bmlmi(ests, D),
        regexp = "`D` must be a numeric larger than 1"
    )


    ## Re-implement bmlmi Var & df implementations to ensure no
    ## small silly coding errors
    local_bmlmi <- function(x, D) {
        B <- length(x) / D
        M <- matrix(x, ncol = D, byrow = TRUE)
        point <- mean(M)
        est_B <- rowMeans(M)
        SSW <- sum(sweep(M, 1, est_B)^2)
        SSB <- D * sum((est_B - mean(est_B))^2)
        MSW <- SSW / ((B * D) - B)
        MSB <- SSB / (B - 1)
        V <- (1 / D) * (MSB * (1 + 1 / B) - MSW)
        df_num <- ((MSB * (B + 1)) - (MSW * B))^2
        df_den_1 <- ((MSB^2) * ((B + 1)^2)) / (B - 1)
        df_den_2 <- (MSW^2 * B) / (D-1)
        df <- df_num / (df_den_1 + df_den_2)
        list(
            est_point = point,
            est_var = V,
            df = max(3, df)
        )
    }

    set.seed(3713)
    x <- c(
        rnorm(100, 10, sd = 2),
        rnorm(100, 5, sd = 7),
        rnorm(100, 20, sd = 3),
        rnorm(100, 30, sd = 4)
    )
    D <- 4
    expect_equal(
        local_bmlmi(x, D),
        get_ests_bmlmi(x, D)
    )

    set.seed(13)
    D <- 4
    x <- rnorm(100, 10, sd = 2)
    expect_equal(
        local_bmlmi(x, D),
        get_ests_bmlmi(x, D)
    )

})





test_that("pool", {
    set.seed(101)

    mu <- 0
    sd <- 1
    n <- 2000
    n_boot <- 5000
    vals <- rnorm(n, mu, sd)

    runanalysis <- function(x) {
        list("p1" = list(est = mean(x), se = sqrt(var(x) / length(x)), df = NA))
    }


    ########  Bootstrap
    results_boot <- as_analysis(
        method = method_condmean(n_samples = 5000),
        results = append(
            list(results = runanalysis(vals)),
            lapply(
                seq_len(n_boot),
                function(x) runanalysis(sample(vals, size = n, replace = TRUE))
            )
        )
    )

    ########  Jackknife
    results_jack <- as_analysis(
        method = method_condmean(type = "jackknife"),
        results = append(
            list(runanalysis(vals)),
            lapply(seq_len(n), function(i) runanalysis(vals[-i]))
        )
    )



    ###### reference CIs
    real_mu <- mean(vals)
    real_se <- sqrt(var(vals) / n)


    boot_norm <- pool(results_boot, type = "normal")
    boot_perc <- pool(results_boot, type = "percentile")
    jack <- pool(results_jack)


    expect_results <- function(res, real_mu, real_se) {
        conf <- res$conf.level

        pars <- res$pars[[1]]

        real_ci <- real_mu + c(-1, 1) * qnorm( (1 - (1 - conf) / 2) * 1.005) * real_se
        ci <- pars$ci


        expect_true(real_ci[1] < ci[1] & real_ci[2] > ci[2])

        expect_true((real_mu - abs(real_mu * 0.01)) < pars$est)
        expect_true((real_mu + abs(real_mu * 0.01)) > pars$est)

    }

    expect_results(boot_norm, real_mu, real_se)
    expect_results(boot_perc, real_mu, real_se)
    expect_results(jack, real_mu, real_se)



    x1 <- pool(results_boot, type = "normal", alternative = "less")
    x2 <- pool(results_boot, type = "percentile", alternative = "less")
    expect_false(identical(x1,x2))


})

test_that("Pool (Rubin) works as expected when se = NA in analysis model", {
    set.seed(101)
  
    mu <- 0
    sd <- 1
    n <- 2000
    vals <- rnorm(n, mu, sd)
    real_mu <- mean(vals)

    runanalysis <- function(x) {
        list("p1" = list(est = mean(x), se = NA, df = NA))
    }

    results_bayes <- as_analysis(
        method = method_bayes(n_samples = 5000),
        results =
            lapply(
                seq_len(5000),
                function(x) runanalysis(sample(vals, size = n, replace = TRUE))
            )
    )
    bayes <- pool(results_bayes)
    bayes2 <- pool(results_bayes, conf.level = 0.8)
    bayes3 <- pool(results_bayes, alternative = "greater")

    expect_equal(
        bayes$pars$p1,
        list(est = real_mu,
             ci = as.numeric(c(NA, NA)),
             se = as.numeric(NA),
             pvalue = as.numeric(NA)),
        tolerance = 1e-2
    )

    expect_equal(
        bayes2$pars$p1,
        list(est = real_mu,
             ci = as.numeric(c(NA, NA)),
             se = as.numeric(NA),
             pvalue = as.numeric(NA)),
        tolerance = 1e-2
    )

    expect_equal(
        bayes3$pars$p1,
        list(est = real_mu,
             ci = as.numeric(c(NA, NA)),
             se = as.numeric(NA),
             pvalue = as.numeric(NA)),
        tolerance = 1e-2
    )

    runanalysis <- function(x) {
        list("p1" = list(est = mean(x), se = NA, df = Inf))
    }

    results_bayes <- as_analysis(
        method = method_bayes(n_samples = 5000),
        results =
            lapply(
                seq_len(5000),
                function(x) runanalysis(sample(vals, size = n, replace = TRUE))
            )
    )
    bayes <- pool(results_bayes)
    bayes2 <- pool(results_bayes, conf.level = 0.8)
    bayes3 <- pool(results_bayes, alternative = "greater")

    expect_equal(
        bayes$pars$p1,
        list(est = real_mu,
             ci = as.numeric(c(NA, NA)),
             se = as.numeric(NA),
             pvalue = as.numeric(NA)),
        tolerance = 1e-2
    )

    expect_equal(
        bayes2$pars$p1,
        list(est = real_mu,
             ci = as.numeric(c(NA, NA)),
             se = as.numeric(NA),
             pvalue = as.numeric(NA)),
        tolerance = 1e-2
    )

    expect_equal(
        bayes3$pars$p1,
        list(est = real_mu,
             ci = as.numeric(c(NA, NA)),
             se = as.numeric(NA),
             pvalue = as.numeric(NA)),
        tolerance = 1e-2
    )
})
  
  test_that("pool BMLMI estimates", {
    set.seed(100)

    mu <- 0
    sd <- 1
    n <- 500
    B <- 1000
    D <- 10

    data <- rnorm(n, mu, sd)

    ############ NO MISSING VALUES

    boot_data <- lapply(seq.int(B), function(x) sample(data, size = n, replace = TRUE))
    vals <- unlist(lapply(boot_data, function(x) lapply(seq.int(D), function(y)
    {
        mu_est <- mean(x, na.rm = TRUE)
        sd_est <- sd(x, na.rm = TRUE)
        x[is.na(x)] <- rnorm(sum(is.na(x)), mu_est, sd_est) # impute
        return(x)
    }
    )
    ), recursive = FALSE)

    runanalysis <- function(x) {
        list("p1" = list(est = mean(x), se = sqrt(var(x) / length(x)), df = NA))
    }


    ########  BMLMI
    results_bmlmi <- as_analysis(
        method = method_bmlmi(B = B, D = D),
        results =
            lapply(
                vals,
                runanalysis
            )
    )

    real_mu <- mean(sapply(vals, mean))

    means_per_boot <- lapply(split(vals, rep(seq.int(B), each = D)), function(x) sapply(x, function(y) mean(y)))
    var_within <- mean(sapply(means_per_boot, function(x) var(x))) # within bootstrap variability (0 if no missing values)
    real_se <- sqrt(mean(sapply(vals[seq(1, B*D, by = D)], function(x) var(x)/n)) + var_within)

    # real_se is the sum of the within and between bootstrap variability.
    # It should be similar to the se estimate from the bmlmi pooling method
    # (exact equality is not proven)

    pooled_res <- pool(results_bmlmi)

    expect_results <- function(res, real_mu, real_se) {
        conf <- res$conf.level

        pars <- res$pars[[1]]

        real_ci <- real_mu + c(-1, 1) * qnorm( (1 - (1 - conf) / 2) * 1.005) * real_se
        ci <- pars$ci

        expect_true(real_ci[1] < ci[1] & real_ci[2] > ci[2])

        expect_true((real_mu - abs(real_mu * 0.01)) < pars$est)
        expect_true((real_mu + abs(real_mu * 0.01)) > pars$est)

    }

    expect_results(pooled_res, real_mu = real_mu, real_se = real_se)



    ############### WITH MISSING VALUES

    data[1:ceiling(n/5)] <- NA # 20% missing values
    boot_data <- lapply(seq.int(B), function(x) sample(data, size = n, replace = TRUE))
    vals <- unlist(lapply(boot_data, function(x) lapply(seq.int(D), function(y)
    {
        mu_est <- mean(x, na.rm = TRUE)
        sd_est <- sd(x, na.rm = TRUE)
        x[is.na(x)] <- rnorm(sum(is.na(x)), mu_est, sd_est) # impute
        return(x)
    }
    )
    ), recursive = FALSE)


    ########  BMLMI
    results_bmlmi <- as_analysis(
        method = method_bmlmi(B = B, D = D),
        results =
            lapply(
                vals,
                runanalysis
            )
    )

    real_mu <- mean(sapply(vals, mean))

    means_per_boot <- lapply(split(vals, rep(seq.int(B), each = D)), function(x) sapply(x, function(y) mean(y)))
    var_within <- mean(sapply(means_per_boot, function(x) var(x))) # within bootstrap variability
    real_se <- sqrt(mean(sapply(vals[seq(1, B*D, by = D)], function(x) var(x)/n)) + var_within)

    # real_se should be similar to the se estimate from the bmlmi pooling method
    # (exact equality is not proven)

    pooled_res <- pool(results_bmlmi)

    expect_results(pooled_res, real_mu = real_mu, real_se = real_se)
    expect_true(sd/sqrt(n) < pooled_res$pars$p1$se)

})

test_that("Can recover known jackknife with  H0 < 0 & H0 > 0", {
    jest <- c( 7, 3, 4 , 5 , 3 ,3 , 9)

    jest_r <- jest[-1]
    jest_m <- mean(jest_r)

    n <- length(jest_r)
    jest_se <- sqrt((sum((jest_r - jest_m)^2) * ((n-1) / n)) )

    expected <- list(
        est = 7,
        ci = 7 + c(-1,Inf) * qnorm(0.9) * jest_se,
        se = jest_se,
        pvalue = pnorm(7, sd = jest_se, lower.tail = TRUE)
    )
    observed <- pool_internal.jackknife(
        list(est = jest),
        conf.level = 0.90,
        alternative = "less"
    )
    expect_equal(observed, expected)



    observed <- pool_internal.jackknife(
        list(est = jest),
        conf.level = 0.90,
        alternative = "greater"
    )
    expected <- list(
        est = 7,
        ci = 7 + c(-Inf,1) * qnorm(0.9) * jest_se,
        se = jest_se,
        pvalue = pnorm(7, sd = jest_se, lower.tail = FALSE)
    )
    expect_equal(observed, expected)



    observed <- pool_internal.jackknife(
        list(est = jest),
        conf.level = 0.90,
        alternative = "two.sided"
    )
    expected <- list(
        est = 7,
        ci = 7 + c(-1,1) * qnorm(0.95) * jest_se,
        se = jest_se,
        pvalue = pnorm(7, sd = jest_se, lower.tail = FALSE) * 2
    )
    expect_equal(observed, expected)

})






test_that("Can recover known values using bootstrap percentiles", {
    best <- c(1,-1,-2,3,2,1,-4,3,2)

    x <- quantile(best[-1], 0.9, type = 6)[[1]]
    pval <- pval_percentile(best[-1])[1]
    names(pval) <- NULL
    expected <- list(
        est = best[1],
        ci = c(-Inf, x),
        se = NA,
        pvalue = pval
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "greater",
        type =  "percentile"
    )
    expect_equal(observed, expected)



    x <- quantile(best[-1], 0.1, type = 6)[[1]]
    pval <- pval_percentile(best[-1])[2]
    names(pval) <- NULL
    expected <- list(
        est = best[1],
        ci = c(x, Inf),
        se = NA,
        pvalue = pval
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "less",
        type =  "percentile"
    )
    expect_equal(observed, expected)



    x1 <- quantile(best[-1], 0.10, type = 6)[[1]]
    x2 <- quantile(best[-1], 0.90, type = 6)[[1]]
    pval <- pval_percentile(best[-1])
    names(pval) <- NULL
    expected <- list(
        est = best[1],
        ci = c(x1, x2),
        se = NA,
        pvalue = min(pval) * 2
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.80,
        alternative = "two.sided",
        type =  "percentile"
    )
    expect_equal(observed, expected)
})



test_that("Results of bootstrap percentiles when n_samples = 0 or 1", {

    ################### n_samples = 0

    best <- c(1)
    expected <- list(
        est = best[1],
        ci = c(NA, NA),
        se = NA,
        pvalue = NA
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "greater",
        type =  "percentile"
    )
    expect_equal(observed, expected)


    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "less",
        type =  "percentile"
    )
    expect_equal(observed, expected)


    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.80,
        alternative = "two.sided",
        type =  "percentile"
    )
    expect_equal(observed, expected)




    ################## n_samples = 1

    best <- c(1,3)
    expected <- list(
        est = best[1],
        ci = c(-Inf, 3),
        se = NA,
        pvalue = 0
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "greater",
        type =  "percentile"
    )
    expect_equal(observed, expected)



    expected <- list(
        est = best[1],
        ci = c(3, Inf),
        se = NA,
        pvalue = 1
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "less",
        type =  "percentile"
    )
    expect_equal(observed, expected)



    expected <- list(
        est = best[1],
        ci = c(3, 3),
        se = NA,
        pvalue = 0
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.80,
        alternative = "two.sided",
        type =  "percentile"
    )
    expect_equal(observed, expected)
}
)

test_that("Bootstrap percentile does not return two-sided p-value larger than 1 when number of positive and negative estimates is equal", {
    best <- c(1,-1,-2,3,2,1,-4,-3,2)

    x1 <- quantile(best[-1], 0.10, type = 6)[[1]]
    x2 <- quantile(best[-1], 0.90, type = 6)[[1]]
    expected <- list(
        est = best[1],
        ci = c(x1, x2),
        se = NA,
        pvalue = 1
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.80,
        alternative = "two.sided",
        type =  "percentile"
    )
    expect_equal(observed, expected)

})




test_that("Can recover known values using bootstrap Normal", {

    best <- c(1,-1,-2,3,2,1,-4,3,2)
    se <- sd(best[-1])

    expected <- list(
        est = best[1],
        ci = best[1] + c(-1,1) * qnorm(0.96) * se,
        se = se,
        pvalue = pnorm(best[1], sd=se, lower.tail = FALSE) * 2
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.92,
        alternative = "two.sided",
        type =  "normal"
    )
    expect_equal(observed, expected)



    expected <- list(
        est = best[1],
        ci = best[1] + c(-1,Inf) * qnorm(0.92) * se,
        se = se,
        pvalue = pnorm(best[1], sd=se, lower.tail = TRUE)
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.92,
        alternative = "less",
        type =  "normal"
    )
    expect_equal(observed, expected)



    expected <- list(
        est = best[1],
        ci = best[1] + c(-Inf,1) * qnorm(0.92) * se,
        se = se,
        pvalue = pnorm(best[1], sd=se, lower.tail = FALSE)
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.92,
        alternative = "greater",
        type =  "normal"
    )
    expect_equal(observed, expected)
})




test_that("Results of bootstrap Normal when n_samples = 0 or 1", {

    ################### n_samples = 0

    best <- c(1)
    expected <- list(
        est = best[1],
        ci = as.numeric(c(NA, NA)),
        se = as.numeric(NA),
        pvalue = as.numeric(NA)
    )
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "greater",
        type =  "normal"
    )
    observed <- lapply(observed, as.numeric)
    expect_equal(observed, expected)


    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "less",
        type =  "normal"
    )
    observed <- lapply(observed, as.numeric)
    expect_equal(observed, expected)


    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.80,
        alternative = "two.sided",
        type =  "normal"
    )
    observed <- lapply(observed, as.numeric)
    expect_equal(observed, expected)




    ################## n_samples = 1

    best <- c(1,3)
    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "greater",
        type =  "normal"
    )
    observed <- lapply(observed, as.numeric)
    expect_equal(observed, expected)


    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.90,
        alternative = "less",
        type =  "normal"
    )
    observed <- lapply(observed, as.numeric)
    expect_equal(observed, expected)


    observed <- pool_internal.bootstrap(
        list(est = best),
        conf.level = 0.80,
        alternative = "two.sided",
        type =  "normal"
    )
    observed <- lapply(observed, as.numeric)
    expect_equal(observed, expected)
}
)




test_that("condmean doesn't use first element in CI", {

    mu <- 5
    sd <- 10
    n <- 200

    runanalysis <- function(x) {
        list("p1" = list(est = mean(x)))
    }

    set.seed(2040)
    x <- as_analysis(
        method = method_condmean(n_samples = 50),
        results = replicate(
            n = 51,
            runanalysis(rnorm(n, mu, sd)),
            simplify = FALSE
        )
    )


    pooled_1 <- pool(x)
    expect_equal(pooled_1$pars$p1$est, x$results[[1]]$p1$est)

    x$results[[1]]$p1$est <- 9999

    pooled_2 <- pool(x)
    expect_equal(pooled_2$pars$p1$est, x$results[[1]]$p1$est)

    pooled_1$pars$p1$est <- NULL
    pooled_2$pars$p1$est <- NULL
    expect_equal(pooled_1, pooled_2)
})

Try the rbmi package in your browser

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

rbmi documentation built on Nov. 24, 2023, 5:11 p.m.