Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cedbf5c
Add Mantel-Fleiss criterion function mantel_fleiss_crit() (No units t…
wwojciech Aug 27, 2026
10fcc22
WORDLIST update (Fleiss)
wwojciech Aug 27, 2026
54e0113
pkgdown update.
wwojciech Aug 27, 2026
301ddbf
update mantel_fleiss_crit() after PR review and add basic unit tests.
wwojciech Aug 27, 2026
1de3a46
Add complete set of unit tests for mantel_fleiss_crit().
wwojciech Aug 27, 2026
cc17869
update mantel_fleiss_crit() after PR review.
wwojciech Aug 27, 2026
1189969
cosmetic update of one unit test for mantel_fleiss_crit()
wwojciech Aug 27, 2026
b206559
Change UI for mantel_fleiss_crit() + update unit tests.
wwojciech Aug 27, 2026
23f6a13
update mantel_fleiss_crit() to return NA when all counts are 0.
wwojciech Aug 27, 2026
35401f9
reorganize NEWS file
wwojciech Aug 28, 2026
c9e4f18
Remove roxygen title tag for mantel_fleiss_crit().
wwojciech Aug 31, 2026
b910f2e
Merge branch 'main' into 1512_add_Mantel_Fleiss_criterion
Melkiades Sep 2, 2026
eef32d0
update mantel_fleiss_crit(): parametrize treshold, add examples of use.
wwojciech Sep 2, 2026
9c2da0a
Update unit test for mantel_fleiss_crit() - new treshold arg.
wwojciech Sep 2, 2026
0bb3faf
Update example for mantel_fleiss_crit()
wwojciech Sep 2, 2026
c244f9b
Merge branch 'main' into 1512_add_Mantel_Fleiss_criterion
wwojciech Sep 9, 2026
6939800
adding vignette
shajoezhu Sep 9, 2026
9ee5f6e
Merge branch 'main' into 1512_add_Mantel_Fleiss_criterion
wwojciech Sep 14, 2026
6392a66
WORDLIST update for the MF vignette.
wwojciech Sep 14, 2026
4e5722d
minor update of the MF vignette.
wwojciech Sep 15, 2026
d6277d7
Update NEWS.md
Melkiades Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ export(labels_use_control)
export(level_order)
export(logistic_regression_cols)
export(logistic_summary_by_flag)
export(mantel_fleiss_crit)
export(month2day)
export(or_clogit)
export(or_glm)
Expand Down
7 changes: 7 additions & 0 deletions NEWS.md
Comment thread
danielinteractive marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# tern 0.9.11.9000

### Enhancements
* Added `mantel_fleiss_crit()` to check the Mantel-Fleiss criterion
for stratified 2 x 2 contingency tables, together with a vignette
demonstrating its use. (#1512)
* Added a `method_only` argument to `d_proportion()`, `d_proportion_diff()`, and
`d_test_proportion_diff()` to allow returning method labels without an additional
description. (#1525)
Expand All @@ -10,6 +13,10 @@
stored in a single column. (#1499)
* Added the `exclude_rows` argument to `g_forest()` to allow excluding selected
rows from the forest plot before plotting. (#1498)

# tern 0.9.11

### Enhancements
* Added `factor_level_method` argument to `df_explicit_na()` to control factor level ordering
when converting character or logical columns. Supported methods: `"sort_auto"` (default,
locale-aware, preserves original behavior), `"sort_radix"` (byte-order / ASCII sort), and
Expand Down
157 changes: 157 additions & 0 deletions R/prop_diff_test.R
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,160 @@ prop_fisher <- function(tbl, alternative = c("two.sided", "less", "greater")) {
tbl <- tbl[, c("TRUE", "FALSE")]
stats::fisher.test(tbl, alternative = alternative)$p.value
}

#' Check the Mantel-Fleiss Criterion
#'
#' @description `r lifecycle::badge("experimental")`
#'
#' Checks the Mantel-Fleiss criterion for stratified 2 x 2 contingency tables.
#'
#' @details
#' The Mantel-Fleiss statistic is calculated as
#'
#' \deqn{
#' MF = \min\left(
#' [\sum_h m_{11h} - \sum_h {(n_{11h})}_L],\
#' [\sum_h {(n_{11h})}_U - \sum_h m_{11h}]
#' \right),
#' }
#'
#' where \eqn{h} indexes the non-empty strata. For each stratum \eqn{h}, the
#' expected frequency of cell \eqn{(1, 1)} in table \eqn{h}, under the
#' hypothesis of no association between group and response, is
#'
#' \deqn{
#' m_{11h} = \frac{n_{1.h} n_{.1h}}{n_h}.
#' }
#'
#' The lower and upper bounds for \eqn{n_{11h}}, given the marginal totals,
#' are:
#'
#' \deqn{
#' {(n_{11h})}_L = \max(0, n_{1.h} - n_{.2h}),
#' }
#' \deqn{
#' {(n_{11h})}_U = \min(n_{.1h}, n_{1.h}).
#' }
#'
#' The Mantel-Fleiss criterion is satisfied when \eqn{MF \ge} `threshold`.
#' By default, `threshold = 5`, corresponding to the criterion described
#' by Mantel and Fleiss (1980).
#'
#' Strata with all cell counts equal to zero are excluded from the
#' calculation. If all strata contain zero observations, there are no
#' non-empty strata over which to calculate the Mantel-Fleiss statistic, and
#' the statistic is therefore undefined. In this case, the function returns
#' `NA`.
#'
#' @param tbl (`array`)\cr
#' A three-dimensional contingency table containing the counts for each
#' combination of group, response, and stratum. The first two dimensions
#' must correspond to the two variables defining the 2 x 2 contingency
#' table (group and response), in either order. The third dimension must
#' correspond to the strata. The first two dimensions must each have exactly
#' two levels. All cell values must be finite, non-missing integer counts.
#' @param include_value (`logical(1)`)\cr
#' Whether to include the calculated Mantel-Fleiss statistic as an attribute
#' of the result.
#' @param threshold (`numeric(1)`)\cr
#' The minimum Mantel-Fleiss statistic required for the criterion to be
#' considered satisfied.
#'
#' @return A logical value indicating whether the Mantel-Fleiss criterion
#' is satisfied. If `include_value = TRUE`, the result also contains a
#' value attribute with the calculated Mantel-Fleiss statistic. If there
#' are no non-empty strata, the result is `NA` and the value attribute is
#' `NA_real_`.
#'
#' @examples
#' set.seed(123)
#' n <- 40
#'
#' grp <- factor(sample(c("Active", "Control"), n, replace = TRUE))
#' rsp <- sample(c(TRUE, FALSE), n, replace = TRUE)
#' strata1 <- factor(sample(c("A", "B"), n, replace = TRUE))
#' strata2 <- factor(sample(c("x", "y"), n, replace = TRUE))
#' strata <- interaction(strata1, strata2)
#'
#' tbl <- table(grp, rsp, strata)
#' tbl
#'
#' is_mf_satisfied <- mantel_fleiss_crit(tbl)
#' is_mf_satisfied
#' mantel_fleiss_crit(tbl, include_value = TRUE)
#'
#' # Examples of use.
#'
#' if (is_mf_satisfied) {
#' print("CMH")
#' prop_diff_cmh(rsp, grp, strata)$prop
#' } else {
#' print("Exact")
#' prop_diff_uncond_exact(rsp, grp)$prop
#' }
#'
Comment thread
Melkiades marked this conversation as resolved.
#' if (is_mf_satisfied) {
#' print("CMH")
#' prop_cmh(tbl)
#' } else {
#' print("Exact")
#' prop_fisher(table(grp, rsp))
#' }
#'
#' @references
#' Mantel, N., and Fleiss, J. L. (1980).
#' Minimum Expected Cell Size Requirements for the Mantel-Haenszel
#' One-Degree-of-Freedom Chi-Square Test and a Related Rapid Procedure.
#' \emph{American Journal of Epidemiology}, 112(1), 129--134.
#'
#' @export
mantel_fleiss_crit <- function(tbl, include_value = FALSE, threshold = 5L) {
checkmate::assert_array(tbl, mode = "integerish", any.missing = FALSE, d = 3L)
checkmate::assert_true(all(tbl >= 0L))
checkmate::assert_true(all(is.finite(tbl)))
checkmate::assert_true(nrow(tbl) == 2L)
checkmate::assert_true(ncol(tbl) == 2L)
checkmate::assert_flag(include_value)
checkmate::assert_number(threshold)

# Drop strata with no observations.
tbl <- tbl[, , apply(tbl, 3L, sum) > 0, drop = FALSE]

# Add marginal totals over the group and response dimensions,
# retaining the stratum dimension.
tbl_mrgn <- stats::addmargins(tbl, margin = 1:2)

# If there are no non-empty strata, the Mantel-Fleiss criterion is undefined
# because there are no strata over which to calculate it.
if (dim(tbl)[3L] == 0L) {
is_satisfied <- NA
if (include_value) {
attr(is_satisfied, "value") <- NA_real_
}
return(is_satisfied)
}

n_1dot <- tbl_mrgn[1L, "Sum", ]
n_dot1 <- tbl_mrgn["Sum", 1L, ]
n_dot2 <- tbl_mrgn["Sum", 2L, ]
n <- tbl_mrgn["Sum", "Sum", ]

# Expected value of n_11 under the hypothesis of no association
# between group and response (within a given stratum).
m_11 <- (n_1dot * n_dot1) / n
# Lower and upper bounds for n_11 given the marginal totals (within a given stratum).
n_11_lwr <- pmax(0L, n_1dot - n_dot2)
n_11_upr <- pmin(n_dot1, n_1dot)

mf_value <- min(
sum(m_11) - sum(n_11_lwr),
sum(n_11_upr) - sum(m_11)
)

is_satisfied <- mf_value >= threshold
if (include_value) {
attr(is_satisfied, "value") <- mf_value
}

is_satisfied
}
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ reference:
- -h_xticks
- -prop_diff
- check_diff_prop_ci
- mantel_fleiss_crit

- title: rtables Helper Functions
desc: These functions help to work with the `rtables` package and may be
Expand Down
10 changes: 10 additions & 0 deletions inst/REFERENCES.bib
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ @article{MantelHaenszel1959
year = {1959}
}

@article{MantelFleiss1980,
title = {Minimum Expected Cell Size Requirements for the Mantel-Haenszel One-Degree-of-Freedom Chi-Square Test and a Related Rapid Procedure},
author = {Mantel, N. and Fleiss, J. L.},
journal = {American Journal of Epidemiology},
volume = {112},
number = {1},
pages = {129--134},
year = {1980}
}

@article{Sato1989,
title = {On the variance estimator for the Mantel-Haenszel Risk Difference},
author = {Sato, Tosiya and Greenland, Sander and Robins, James M.},
Expand Down
3 changes: 3 additions & 0 deletions inst/WORDLIST
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ CDISC
CMH
CQ
Clopper
Cochran
Coull
Fleiss
Haenszel
Hauck
Hilferty
Hoffmann
Jeffreys
Kaplan
Kenward
MF
MMRM
MedDRA
Miettinen
Expand Down
117 changes: 117 additions & 0 deletions man/mantel_fleiss_crit.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading