Skip to content

Commit

Permalink
Add method argument to s_odds_ratio() and estimate_odds_ratio() (
Browse files Browse the repository at this point in the history
…#1320)

# Pull Request

Fixes #1314
  • Loading branch information
edelarua authored Oct 7, 2024
1 parent e6a3d07 commit c094fe2
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 55 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Imports:
rlang (>= 1.1.0),
scales (>= 1.2.0),
stats,
survival (>= 3.2-13),
survival (>= 3.7-0),
tibble (>= 2.0.0),
tidyr (>= 0.8.3),
utils
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Refactored `estimate_incidence_rate` to work as both an analyze function and a summarize function, controlled by the added `summarize` parameter. When `summarize = TRUE`, labels can be fine-tuned via the new `label_fmt` argument to the same function.
* Added `fraction` statistic to the `analyze_var_count` method group.
* Improved `summarize_glm_count()` documentation and all its associated functions to better describe the results and the functions' purpose.
* Added `method` argument to `s_odds_ratio()` and `estimate_odds_ratio()` to control whether exact or approximate conditional likelihood calculations are used.

### Bug Fixes
* Added defaults for `d_count_cumulative` parameters as described in the documentation.
Expand Down
27 changes: 19 additions & 8 deletions R/odds_ratio.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#' @inheritParams argument_convention
#' @param .stats (`character`)\cr statistics to select for the table. Run `get_stats("estimate_odds_ratio")`
#' to see available statistics for this function.
#' @param method (`string`)\cr whether to use the correct (`"exact"`) calculation in the conditional likelihood or one
#' of the approximations. See [survival::clogit()] for details.
#'
#' @note
#' * This function uses logistic regression for unstratified analyses, and conditional logistic regression for
Expand Down Expand Up @@ -64,7 +66,8 @@ s_odds_ratio <- function(df,
.df_row,
variables = list(arm = NULL, strata = NULL),
conf_level = 0.95,
groups_list = NULL) {
groups_list = NULL,
method = "exact") {
y <- list(or_ci = "", n_tot = "")

if (!.in_ref_col) {
Expand All @@ -83,6 +86,7 @@ s_odds_ratio <- function(df,
y <- or_glm(data, conf_level = conf_level)
} else {
assert_df_with_variables(.df_row, c(list(rsp = .var), variables))
checkmate::assert_subset(method, c("exact", "approximate", "efron", "breslow"), empty.ok = FALSE)

# The group variable prepared for clogit must be synchronised with combination groups definition.
if (is.null(groups_list)) {
Expand Down Expand Up @@ -118,14 +122,21 @@ s_odds_ratio <- function(df,
grp = grp,
strata = interaction(.df_row[variables$strata])
)
y_all <- or_clogit(data, conf_level = conf_level)
y_all <- or_clogit(data, conf_level = conf_level, method = method)
checkmate::assert_string(trt_grp)
checkmate::assert_subset(trt_grp, names(y_all$or_ci))
y$or_ci <- y_all$or_ci[[trt_grp]]
y$n_tot <- y_all$n_tot
}
}

if ("est" %in% names(y$or_ci) && is.na(y$or_ci[["est"]]) && method != "approximate") {
warning(
"Unable to compute the odds ratio estimate. Please try re-running the function with ",
'parameter `method` set to "approximate".'
)
}

y$or_ci <- formatters::with_label(
x = y$or_ci,
label = paste0("Odds Ratio (", 100 * conf_level, "% CI)")
Expand Down Expand Up @@ -163,8 +174,6 @@ a_odds_ratio <- make_afun(
#' @describeIn odds_ratio Layout-creating function which can take statistics function arguments
#' and additional format arguments. This function is a wrapper for [rtables::analyze()].
#'
#' @param ... arguments passed to `s_odds_ratio()`.
#'
#' @return
#' * `estimate_odds_ratio()` returns a layout object suitable for passing to further layouting functions,
#' or to [rtables::build_table()]. Adding this function to an `rtable` layout will add formatted rows containing
Expand Down Expand Up @@ -193,15 +202,15 @@ estimate_odds_ratio <- function(lyt,
groups_list = NULL,
na_str = default_na_str(),
nested = TRUE,
...,
method = "exact",
show_labels = "hidden",
table_names = vars,
var_labels = vars,
.stats = "or_ci",
.formats = NULL,
.labels = NULL,
.indent_mods = NULL) {
extra_args <- list(variables = variables, conf_level = conf_level, groups_list = groups_list, ...)
extra_args <- list(variables = variables, conf_level = conf_level, groups_list = groups_list, method = method)

afun <- make_afun(
a_odds_ratio,
Expand Down Expand Up @@ -230,6 +239,7 @@ estimate_odds_ratio <- function(lyt,
#'
#' Functions to calculate odds ratios in [estimate_odds_ratio()].
#'
#' @inheritParams odds_ratio
#' @inheritParams argument_convention
#' @param data (`data.frame`)\cr data frame containing at least the variables `rsp` and `grp`, and optionally
#' `strata` for [or_clogit()].
Expand Down Expand Up @@ -300,19 +310,20 @@ or_glm <- function(data, conf_level) {
#' or_clogit(data, conf_level = 0.95)
#'
#' @export
or_clogit <- function(data, conf_level) {
or_clogit <- function(data, conf_level, method = "exact") {
checkmate::assert_logical(data$rsp)
assert_proportion_value(conf_level)
assert_df_with_variables(data, list(rsp = "rsp", grp = "grp", strata = "strata"))
checkmate::assert_multi_class(data$grp, classes = c("factor", "character"))
checkmate::assert_multi_class(data$strata, classes = c("factor", "character"))
checkmate::assert_subset(method, c("exact", "approximate", "efron", "breslow"), empty.ok = FALSE)

data$grp <- as_factor_keep_attributes(data$grp)
data$strata <- as_factor_keep_attributes(data$strata)

# Deviation from convention: `survival::strata` must be simply `strata`.
formula <- stats::as.formula("rsp ~ grp + strata(strata)")
model_fit <- clogit_with_tryCatch(formula = formula, data = data)
model_fit <- clogit_with_tryCatch(formula = formula, data = data, method = method)

# Create a list with one set of OR estimates and CI per coefficient, i.e.
# comparison of one group vs. the reference group.
Expand Down
5 changes: 4 additions & 1 deletion man/h_odds_ratio.Rd

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

11 changes: 7 additions & 4 deletions man/odds_ratio.Rd

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

9 changes: 9 additions & 0 deletions tests/testthat/_snaps/odds_ratio.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,12 @@
———————————————————————————————————————————————————————————
Odds Ratio (95% CI) 1.24 (0.54 - 2.89)

# estimate_odds_ratio method argument works

Code
res
Output
A B
————————————————————————————————————————————
Odds Ratio (95% CI) 0.96 (0.85 - 1.08)

121 changes: 80 additions & 41 deletions tests/testthat/test-odds_ratio.R
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ testthat::test_that("or_clogit estimates right OR and CI", {
stringsAsFactors = TRUE
)

# https://github.com/therneau/survival/issues/240
withr::with_options(
opts_partial_match_old,
result <- or_clogit(data, conf_level = 0.95)
)
result <- or_clogit(data, conf_level = 0.95)

# from SAS
res <- testthat::expect_silent(result)
Expand Down Expand Up @@ -66,17 +62,13 @@ testthat::test_that("s_odds_ratio estimates right OR and CI (stratified analysis
strata = factor(sample(c("C", "D"), 100, TRUE))
)

# https://github.com/therneau/survival/issues/240
withr::with_options(
opts_partial_match_old,
result <- s_odds_ratio(
df = subset(dta, grp == "A"),
.var = "rsp",
.ref_group = subset(dta, grp == "B"),
.in_ref_col = FALSE,
.df_row = dta,
variables = list(arm = "grp", strata = "strata")
)
result <- s_odds_ratio(
df = subset(dta, grp == "A"),
.var = "rsp",
.ref_group = subset(dta, grp == "B"),
.in_ref_col = FALSE,
.df_row = dta,
variables = list(arm = "grp", strata = "strata")
)

res <- testthat::expect_silent(result)
Expand All @@ -94,19 +86,15 @@ testthat::test_that("s_odds_ratio returns error for incorrect groups", {
"Arms A+B" = c("A", "B")
)

# https://github.com/therneau/survival/issues/240
withr::with_options(
opts_partial_match_old,
result <- testthat::expect_error(s_odds_ratio(
df = subset(data, grp == "A"),
.var = "rsp",
.ref_group = subset(data, grp == "B"),
.in_ref_col = FALSE,
.df_row = data,
variables = list(arm = "grp", strata = "strata"),
groups_list = groups
))
)
testthat::expect_error(result <- s_odds_ratio(
df = subset(data, grp == "A"),
.var = "rsp",
.ref_group = subset(data, grp == "B"),
.in_ref_col = FALSE,
.df_row = data,
variables = list(arm = "grp", strata = "strata"),
groups_list = groups
))
})

testthat::test_that("estimate_odds_ratio estimates right OR and CI (unstratified analysis)", {
Expand All @@ -132,14 +120,10 @@ testthat::test_that("estimate_odds_ratio estimates right OR and CI (stratified a
strata = factor(sample(c("C", "D"), 100, TRUE))
)

# https://github.com/therneau/survival/issues/240
withr::with_options(
opts_partial_match_old,
result <- basic_table() %>%
split_cols_by(var = "grp", ref_group = "A", split_fun = ref_group_position("first")) %>%
estimate_odds_ratio(vars = "rsp", variables = list(arm = "grp", strata = "strata")) %>%
build_table(df = data)
)
result <- basic_table() %>%
split_cols_by(var = "grp", ref_group = "A", split_fun = ref_group_position("first")) %>%
estimate_odds_ratio(vars = "rsp", variables = list(arm = "grp", strata = "strata")) %>%
build_table(df = data)

res <- testthat::expect_silent(result)
testthat::expect_snapshot(res)
Expand Down Expand Up @@ -170,12 +154,67 @@ testthat::test_that("estimate_odds_ratio works with strata and combined groups",
groups_list = groups
)

# https://github.com/therneau/survival/issues/240
withr::with_options(
opts_partial_match_old,
result <- build_table(lyt = lyt, df = anl)
result <- build_table(lyt = lyt, df = anl)

res <- testthat::expect_silent(result)
testthat::expect_snapshot(res)
})

testthat::test_that("s_odds_ratio method argument works", {
set.seed(1)
nex <- 2000 # Number of example rows
dta <- data.frame(
"rsp" = sample(c(TRUE, FALSE), nex, TRUE),
"grp" = sample(c("A", "B"), nex, TRUE),
"f1" = sample(c("a1", "a2"), nex, TRUE),
"f2" = sample(c("x", "y", "z"), nex, TRUE),
strata = factor(sample(c("C", "D"), nex, TRUE)),
stringsAsFactors = TRUE
)

res <- s_odds_ratio(
df = subset(dta, grp == "A"),
.var = "rsp",
.ref_group = subset(dta, grp == "B"),
.in_ref_col = FALSE,
.df_row = dta,
variables = list(arm = "grp", strata = "strata"),
method = "approximate"
)

testthat::expect_false(all(is.na(res$or_ci)))

# warning works
expect_warning(
s_odds_ratio(
df = subset(dta, grp == "A"),
.var = "rsp",
.ref_group = subset(dta, grp == "B"),
.in_ref_col = FALSE,
.df_row = dta,
variables = list(arm = "grp", strata = "strata")
)
)
})

testthat::test_that("estimate_odds_ratio method argument works", {
nex <- 2000 # Number of example rows
set.seed(12)
dta <- data.frame(
"rsp" = sample(c(TRUE, FALSE), nex, TRUE),
"grp" = sample(c("A", "B"), nex, TRUE),
"f1" = sample(c("a1", "a2"), nex, TRUE),
"f2" = sample(c("x", "y", "z"), nex, TRUE),
strata = factor(sample(c("C", "D"), nex, TRUE)),
stringsAsFactors = TRUE
)

lyt <- basic_table() %>%
split_cols_by(var = "grp", ref_group = "B") %>%
estimate_odds_ratio(vars = "rsp", variables = list(arm = "grp", strata = "strata"), method = "approximate")

result <- build_table(lyt, df = dta)

res <- testthat::expect_silent(result)
testthat::expect_snapshot(res)
})

0 comments on commit c094fe2

Please sign in to comment.