tests/testthat/test-CMHtest.R

# CMHtest ----

test_that("CMHtest automatically filters out strata with single observations", {
  data <- data.frame(
    A = c("a", "a", "b", "b", "b", "c"),
    Y = c(1, 2, 3, 4, 5, 6),
    B = c("s1", "s1", "s2", "s2", "s2", "s3"),
    C = c(10, 20, 30, 40, 50, 60)
  )
  result <- expect_silent(CMHtest(
    C ~ A | B,
    data = data,
    overall = TRUE
  ))
  expect_type(result, "list")
  # B:s3 has been removed automatically.
  expect_identical(names(result), c("B:s1", "B:s2", "ALL"))
})

test_that("CMHtest works with multiple and sparse strata variables", {
  data <- data.frame(
    A = c("a", "a", "b", "b", "b", "c", "c", "c"),
    Y = c(1, 2, 3, 4, 5, 6, 7, 8),
    B1 = c("s1", "s1", "s2", "s2", "s2", "s1", "s1", "s1"),
    B2 = c("t1", "t1", "t1", "t1", "t1", "t2", "t2", "t2"),
    C = c(10, 20, 30, 40, 50, 60, 70, 80)
  )
  result <- expect_silent(CMHtest(
    C ~ A | B1 + B2,
    data = data,
    overall = TRUE
  ))
  expect_type(result, "list")
  # B1:s2 & B2:t2 combination has been removed automatically.
  expect_identical(
    names(result),
    c("B1:s1|B2:t1", "B1:s2|B2:t1", "B1:s1|B2:t2", "ALL")
  )
})

# integration tests vs. SAS results ----

# The test cases have been published first in the CAMIS project
# (https://psiaims.github.io/CAMIS/)
# The CAMIS test code has been written by
# Christina Fillmore, Lyn Taylor, Yannick Vandendijck and Logan Johnson.
# The work is published at https://github.com/PSIAIMS/CAMIS/blob/main/R/cmh.qmd
# under the Apache License 2.0.
# The code has been adapted here in order to fit into the testthat framework.

data <- read.csv(testthat::test_path("data", "adcibc.csv"))
data2 <- data %>%
  dplyr::filter(
    TRTPN != "54",
    AGEGR1 != ">80"
  )

sas_results <- tibble::tribble(
  ~Scenario , ~Test                 , ~Chisq , ~Df , ~Prob  ,
  1L        , "Correlation"         , 0.2166 ,   1 , 0.6417 ,
  1L        , "Row Means"           , 0.2166 ,   1 , 0.6417 ,
  1L        , "General Association" , 0.2166 ,   1 , 0.6417 ,

  2L        , "Correlation"         , 0.0009 ,   1 , 0.9765 ,
  2L        , "Row Means"           , 2.4820 ,   2 , 0.2891 , # corrected Df
  2L        , "General Association" , 2.4820 ,   2 , 0.2891 , # corrected Df

  3L        , "Correlation"         , 0.0028 ,   1 , 0.9579 ,
  3L        , "Row Means"           , 2.3861 ,   2 , 0.3033 ,
  3L        , "General Association" , 2.3861 ,   2 , 0.3033 ,

  4L        , "Correlation"         , 1.7487 ,   1 , 0.1860 ,
  4L        , "Row Means"           , 1.7487 ,   1 , 0.1860 ,
  4L        , "General Association" , 8.0534 ,   4 , 0.0896 ,

  5L        , "Correlation"         , 0.0854 ,   1 , 0.7701 ,
  5L        , "Row Means"           , 2.4763 ,   2 , 0.2899 ,
  5L        , "General Association" , 7.0339 ,   8 , 0.5330 ,

  6L        , "Correlation"         , 1.6621 ,   1 , 0.1973 ,
  6L        , "Row Means"           , 2.2980 ,   4 , 0.6811 ,
  6L        , "General Association" , 5.7305 ,   8 , 0.6774
)

convert_to_tibble <- function(cmh_test) {
  table <- cmh_test$ALL$table
  result <- tibble::as_tibble(table) %>%
    dplyr::mutate(
      dplyr::across(dplyr::everything(), unlist),
      Test = rownames(table),
      Test = dplyr::case_when(
        Test == "cor" ~ "Correlation",
        Test == "rmeans" ~ "Row Means",
        Test == "cmeans" ~ "Column Means",
        Test == "general" ~ "General Association"
      )
    )
  result[
    match(c("Correlation", "Row Means", "General Association"), result$Test),
    c("Test", "Chisq", "Df", "Prob")
  ]
}

get_sas_results <- function(scenario) {
  sas_results %>%
    dplyr::filter(Scenario == scenario) %>%
    dplyr::select(Test, Chisq, Df, Prob)
}

test_that("2x2x2 schema, rather balanced groups", {
  result <- convert_to_tibble(CMHtest(
    Freq ~ TRTP + SEX | AGEGR1,
    data = data2,
    overall = TRUE
  ))
  expected <- get_sas_results(1L)
  expect_equal(result, expected, tolerance = 1e-3)
})

test_that("3x2x3 schema, rather balanced groups", {
  result <- convert_to_tibble(vcdExtra::CMHtest(
    Freq ~ TRTP + SEX | AGEGR1,
    data = data,
    overall = TRUE
  ))
  expected <- get_sas_results(2L)
  expect_equal(result, expected, tolerance = 1e-3)
})

test_that("3x2x3 schema, one stratum with a single observation automatically omitted", {
  stopifnot(any(table(data$RACE) == 1))
  result <- convert_to_tibble(vcdExtra::CMHtest(
    Freq ~ TRTP + SEX | RACE,
    data = data,
    overall = TRUE
  ))
  expected <- get_sas_results(3L)
  expect_equal(result, expected, tolerance = 1e-3)
})

test_that("2x5x2 schema, ordinal variable", {
  result <- convert_to_tibble(vcdExtra::CMHtest(
    Freq ~ TRTP + AVAL | SEX,
    data = data2,
    overall = TRUE
  ))
  expected <- get_sas_results(4L)
  expect_equal(result, expected, tolerance = 1e-3)
})

test_that("3x5x17 schema, sparse groups, ordinal variable", {
  stopifnot(any(table(data$SITEID) == 1))
  result <- convert_to_tibble(vcdExtra::CMHtest(
    Freq ~ TRTP + AVAL | SITEID,
    data = data,
    overall = TRUE
  ))
  expected <- get_sas_results(5L)
  expect_equal(result, expected, tolerance = 1e-3)
})

test_that("5x3x3 schema, ordinal variables", {
  result <- convert_to_tibble(vcdExtra::CMHtest(
    Freq ~ AVAL + AGEGR1N | TRTP,
    data = data,
    overall = TRUE
  ))
  expected <- get_sas_results(6L)
  expect_equal(result, expected, tolerance = 1e-3)
})

Try the vcdExtra package in your browser

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

vcdExtra documentation built on Dec. 11, 2025, 9:06 a.m.