Skip to content

Commit

Permalink
Refactor cox regression functions to fix table structure (#882)
Browse files Browse the repository at this point in the history
Closes #841 

---------

Co-authored-by: 27856297+dependabot-preview[bot]@users.noreply.github.com <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Joe Zhu <joe.zhu@roche.com>
Co-authored-by: Melkiades <davide.garolini@roche.com>
  • Loading branch information
4 people authored Apr 26, 2023
1 parent 924e7c0 commit b1c8063
Show file tree
Hide file tree
Showing 10 changed files with 1,035 additions and 454 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Collate:
'summarize_ancova.R'
'summarize_change.R'
'summarize_colvars.R'
'summarize_coxreg.R'
'summarize_glm_count.R'
'summarize_num_patients.R'
'summarize_patients_exposure_in_cols.R'
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# tern 0.8.1.9008

### Breaking Changes
* Refactored `s_coxreg` and `summarize_coxreg`. Added new analysis function `a_coxreg`.

### Bug Fixes
* Fixed missing label for `TRTEDTM` in `tern` datasets.

Expand Down
214 changes: 0 additions & 214 deletions R/cox_regression.R
Original file line number Diff line number Diff line change
@@ -1,217 +1,3 @@
#' Cox Proportional Hazards Regression
#'
#' @description `r lifecycle::badge("stable")`
#'
#' Fits a Cox regression model and estimate hazard ratio to describe the effect
#' size in a survival analysis.
#'
#' @details
#' Cox models are the most commonly used methods to estimate the magnitude of
#' the effect in survival analysis. It assumes proportional hazards: the ratio
#' of the hazards between groups (e.g., two arms) is constant over time.
#' This ratio is referred to as the "hazard ratio" (HR) and is one of the
#' most commonly reported metrics to describe the effect size in survival
#' analysis (NEST Team, 2020).
#'
#' @note The usual formatting arguments for the _layout creating_ function
#' `summarize_coxreg` are not yet accepted (`.stats`, `.indent_mod`, `.formats`,
#' `.labels`).
#' @inheritParams argument_convention
#' @seealso [fit_coxreg] for relevant fitting functions, [h_cox_regression] for relevant
#' helper functions, and [tidy_coxreg] for custom tidy methods.
#'
#' @name cox_regression
#'
#' @examples
#' library(survival)
#'
#' # Testing dataset [survival::bladder].
#' set.seed(1, kind = "Mersenne-Twister")
#' dta_bladder <- with(
#' data = bladder[bladder$enum < 5, ],
#' data.frame(
#' time = stop,
#' status = event,
#' armcd = as.factor(rx),
#' covar1 = as.factor(enum),
#' covar2 = factor(
#' sample(as.factor(enum)),
#' levels = 1:4, labels = c("F", "F", "M", "M")
#' )
#' )
#' )
#' labels <- c("armcd" = "ARM", "covar1" = "A Covariate Label", "covar2" = "Sex (F/M)")
#' formatters::var_labels(dta_bladder)[names(labels)] <- labels
#' dta_bladder$age <- sample(20:60, size = nrow(dta_bladder), replace = TRUE)
#'
#' plot(
#' survfit(Surv(time, status) ~ armcd + covar1, data = dta_bladder),
#' lty = 2:4,
#' xlab = "Months",
#' col = c("blue1", "blue2", "blue3", "blue4", "red1", "red2", "red3", "red4")
#' )
NULL

#' @describeIn cox_regression transforms the tabulated results from [`fit_coxreg_univar()`]
#' and [`fit_coxreg_multivar()`] into a list. Not much calculation is done here,
#' it rather prepares the data to be used by the layout creating function.
#' @export
#'
#' @examples
#' # s_coxreg
#'
#' univar_model <- fit_coxreg_univar(
#' variables = list(
#' time = "time", event = "status", arm = "armcd",
#' covariates = c("covar1", "covar2")
#' ),
#' data = dta_bladder
#' )
#' df1 <- broom::tidy(univar_model)
#' s_coxreg(df = df1, .var = "hr")
#'
#' # Only covariates.
#' univar_covs_model <- fit_coxreg_univar(
#' variables = list(
#' time = "time", event = "status",
#' covariates = c("covar1", "covar2")
#' ),
#' data = dta_bladder
#' )
#' df1_covs <- broom::tidy(univar_covs_model)
#' s_coxreg(df = df1_covs, .var = "hr")
#'
#' # Multivariate.
#' multivar_model <- fit_coxreg_multivar(
#' variables = list(
#' time = "time", event = "status", arm = "armcd",
#' covariates = c("covar1", "covar2")
#' ),
#' data = dta_bladder
#' )
#' df2 <- broom::tidy(multivar_model)
#' s_coxreg(df = df2, .var = "hr")
#'
#' # Multivariate without treatment arm.
#' multivar_covs_model <- fit_coxreg_multivar(
#' variables = list(
#' time = "time", event = "status",
#' covariates = c("covar1", "covar2")
#' ),
#' data = dta_bladder
#' )
#' df2_covs <- broom::tidy(multivar_covs_model)
#' s_coxreg(df = df2_covs, .var = "hr")
s_coxreg <- function(df, .var) {
assert_df_with_variables(df, list(term = "term", var = .var))
checkmate::assert_multi_class(df$term, classes = c("factor", "character"))
df$term <- as.character(df$term)
# We need a list with names corresponding to the stats to display.
# There can be several covariate to test, but the names of the items should
# be constant and equal to the stats to display.
y <- split(df, f = df$term, drop = FALSE)
y <- stats::setNames(y, nm = rep(.var, length(y)))
lapply(
X = y,
FUN = function(x) {
z <- as.list(x[[.var]])
stats::setNames(z, nm = x$term_label)
}
)
}

#' @describeIn cox_regression layout creating function.
#' @inheritParams argument_convention
#' @inheritParams control_coxreg
#' @param multivar (`flag`)\cr if `TRUE`, the multi-variable Cox regression will run
#' and no interaction will be considered between the studied treatment and c
#' candidate covariate. Default is `FALSE` for univariate Cox regression including
#' an arm variable. When no arm variable is included in the univariate Cox regression,
#' then also `TRUE` should be used to tabulate the covariate effect estimates instead
#' of the treatment arm effect estimate across models.
#' @param vars (`character`)\cr the name of statistics to be reported among
#' `n` (number of observation),
#' `hr` (Hazard Ratio),
#' `ci` (confidence interval),
#' `pval` (p.value of the treatment effect) and
#' `pval_inter` (the p.value of the interaction effect between the treatment
#' and the covariate).
#' @export
#'
#' @examples
#' # summarize_coxreg
#' result_univar <- basic_table() %>%
#' split_rows_by("effect") %>%
#' split_rows_by("term", child_labels = "hidden") %>%
#' summarize_coxreg(conf_level = 0.95) %>%
#' build_table(df1)
#' result_univar
#'
#' result_multivar <- basic_table() %>%
#' split_rows_by("term", child_labels = "hidden") %>%
#' summarize_coxreg(multivar = TRUE, conf_level = .95) %>%
#' build_table(df2)
#' result_multivar
#'
#' # When tabulating univariate models with only covariates, also `multivar = TRUE`
#' # is used.
#' result_univar_covs <- basic_table() %>%
#' split_rows_by("term", child_labels = "hidden") %>%
#' summarize_coxreg(multivar = TRUE, conf_level = 0.95) %>%
#' build_table(df1_covs)
#' result_univar_covs
#'
#' # No change for the multivariate tabulation when no treatment arm is included.
#' result_multivar_covs <- basic_table() %>%
#' split_rows_by("term", child_labels = "hidden") %>%
#' summarize_coxreg(multivar = TRUE, conf_level = .95) %>%
#' build_table(df2_covs)
#' result_multivar_covs
summarize_coxreg <- function(lyt,
conf_level,
multivar = FALSE,
vars = c("n", "hr", "ci", "pval")) {
afun_list <- Map(
function(stat, format) {
make_afun(s_coxreg, .stats = stat, .formats = format, .ungroup_stats = stat)
},
stat = c("n", "hr", "ci", "pval", "pval_inter"),
format = c(
n = "xx",
hr = "xx.xx",
ci = "(xx.xx, xx.xx)",
pval = "x.xxxx | (<0.0001)",
pval_inter = "x.xxxx | (<0.0001)"
)
)

if (multivar) {
vars <- intersect(c("hr", "ci", "pval"), vars)
lyt <- split_cols_by_multivar(
lyt = lyt,
vars = vars,
varlabels = c(
hr = "Hazard Ratio",
ci = paste0(100 * conf_level, "% CI"),
pval = "p-value"
)[vars]
)
} else {
lyt <- split_cols_by_multivar(
lyt = lyt,
vars = vars,
varlabels = c(
n = "n", hr = "Hazard Ratio",
ci = paste0(100 * conf_level, "% CI"),
pval = "p-value",
pval_inter = "Interaction p-value"
)[vars]
)
}

analyze_colvars(lyt = lyt, afun = afun_list[vars])
}

#' Controls for Cox Regression
#'
#' @description `r lifecycle::badge("stable")`
Expand Down
Loading

0 comments on commit b1c8063

Please sign in to comment.