Skip to content

Commit

Permalink
Adding Surv_CNSR() function (#397)
Browse files Browse the repository at this point in the history
* Create Surv_CDISC.R

* updates

* doc update

* Re-build README.Rmd

* Update Surv_CDISC.R

* Re-build README.Rmd

* Update Surv_CDISC.R

* Re-build README.Rmd

* doc updates

* Re-build README.Rmd

* Update NAMESPACE

* merge conflict fix

* Re-build README.Rmd

* updates

* Re-build README.Rmd

* Update test-Surv_CDISC.R

* Re-build README.Rmd

* readme

* doc updates from review

* doc updates

* Update test-Surv_CDISC.R

* Update testing:
- consistent with CDISC philosophy
- check the actual requirement
- remove req T2.5 as this is not a requirement of our function, but something tested in the backend of survival

* removed requirement 2.3 in TOC

* change function name

* Update testing
Included CNSR info in estimate_KM to ensure user will restrict to 0/1

* re-documenting

Co-authored-by: GitHub Actions <[email protected]>
Co-authored-by: Mark Baillie <[email protected]>
Co-authored-by: shaesen2 <[email protected]>
  • Loading branch information
4 people authored Jun 15, 2022
1 parent 2bd9b15 commit d6a3c6c
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 2 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ S3method(visr,survfit)
S3method(visr,tidycuminc)
export("%>%")
export(Surv)
export(Surv_CNSR)
export(add_CI)
export(add_CNSR)
export(add_annotation)
Expand Down
77 changes: 77 additions & 0 deletions R/Surv_CNSR.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#' Create a Survival Object from CDISC Data
#'
#' @description
#' `r lifecycle::badge('experimental')`
#'
#' The aim of `Surv_CNSR()` is to map the inconsistency in convention between
#' the [survival](https://cran.r-project.org/package=survival) package and
#' [CDISC ADaM ADTTE data model](https://www.cdisc.org/standards/foundational/adam/adam-basic-data-structure-bds-time-event-tte-analyses-v1-0).
#'
#' The function creates a survival object (e.g. `survival::Surv()`) that
#' uses CDISC ADaM ADTTE coding conventions and converts the arguments to the
#' status/event variable convention used in the
#' [survival](https://cran.r-project.org/package=survival) package.
#'
#' The `AVAL` and `CNSR` arguments are passed to
#' `survival::Surv(time = AVAL, event = 1 - CNSR, type = "right", origin = 0)`.
#'
#' @section Details:
#'
#' The `Surv_CNSR()` function creates a survival object utilizing the
#' expected data structure in the CDISC ADaM ADTTE data model,
#' mapping the CDISC ADaM ADTTE coding conventions with the expected
#' status/event variable convention used in the survival package---specifically,
#' the coding convention used for the status/event indicator.
#' The survival package expects the status/event indicator in the
#' following format: `0=alive`, `1=dead`. Other accepted choices are
#' `TRUE`/`FALSE` (`TRUE = death`) or `1`/`2` (`2=death`).
#' A final but risky option is to omit the indicator variable, in which case
#' all subjects are assumed to have an event.
#'
#' The CDISC ADaM ADTTE data model adopts a different coding convention for
#' the event/status indicator. Using this convention, the event/status variable
#' is named `'CNSR'` and uses the following coding: `censor = 1`, `status/event = 0`.
#'
#' @param AVAL The follow-up time. The follow-up time is assumed to originate from zero.
#' When no argument is passed, the default value is a column/vector named `AVAL`.
#' @param CNSR The censoring indicator where `1=censored` and `0=death/event`.
#' When no argument is passed, the default value is a column/vector named `CNSR`.
#'
#' @return Object of class 'Surv'
#' @seealso [`survival::Surv()`], [`estimate_KM()`]
#' @export
#'
#' @examples
#' # Use the `Surv_CNSR()` function with visR functions
#' adtte %>%
#' visR:: estimate_KM(formula = visR::Surv_CNSR() ~ SEX)
#'
#' # Use the `Surv_CNSR()` function with functions from other packages as well
#' survival::survfit(visR::Surv_CNSR() ~ SEX, data = adtte)
#' survival::survreg(visR::Surv_CNSR() ~ SEX + AGE, data = adtte) %>%
#' broom::tidy()

Surv_CNSR <- function(AVAL, CNSR) {
# set default values if not passed by user -----------------------------------
if (missing(AVAL) && exists("AVAL", envir = rlang::caller_env()))
AVAL <- get("AVAL", envir = rlang::caller_env())
else if (missing(AVAL))
stop("Default 'AVAL' value not found. Specify argument in `Surv_CNSR(AVAL=)`.")
if (missing(CNSR) && exists("CNSR", envir = rlang::caller_env()))
CNSR <- get("CNSR", envir = rlang::caller_env())
else if (missing(CNSR))
stop("Default 'CNSR' value not found. Specify argument in `Surv_CNSR(CNSR=)`.")

# checking inputs ------------------------------------------------------------
if (!is.numeric(AVAL) || !is.numeric(CNSR))
stop("Expecting arguments 'AVAL' and 'CNSR' to be numeric.")

if (stats::na.omit(CNSR) %>% setdiff(c(0, 1)) %>% {!rlang::is_empty(.)})
stop("Expecting 'CNSR' argument to be binary with values `0/1`.")

if (any(AVAL < 0))
warning("Values of 'AVAL' are less than zero, which is likely a data error.")

# pass args to `survival::Surv()` --------------------------------------------
survival::Surv(time = AVAL, event = 1 - CNSR, type = "right", origin = 0)
}
3 changes: 2 additions & 1 deletion R/estimate_KM.R
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
#' @param AVAL,CNSR,strata These arguments are used to construct a formula to be passed to
#' `survival::survfit(formula=Surv(AVAL, 1-CNSR)~strata)`. These arguments' default values follow the naming conventions in CDISC.
#' - `AVAL` Analysis value for Time-to-Event analysis. Default is `"AVAL"`, as per CDISC ADaM guiding principles.
#' - `CNSR` Censor for Time-to-Event analysis. Default is `"CNSR"`, as per CDISC ADaM guiding principles.
#' - `CNSR` Censor for Time-to-Event analysis. Default is `"CNSR"`, as per CDISC ADaM guiding principles. It is expected that CNSR = 1
#' for censoring and CNSR = 0 for the event of interest.
#' - `strata` Character vector, representing the strata for Time-to-Event analysis. When NULL, an overall analysis is performed.
#' Default is `NULL`.
#' @param ... additional arguments passed on to the ellipsis of the call `survival::survfit.formula(...)`.
Expand Down
1 change: 1 addition & 0 deletions inst/WORDLIST
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ RGBA
RRGGBB
RRGGBBAA
SDTM
Surv
Tarone
UCL
UX
Expand Down
65 changes: 65 additions & 0 deletions man/Surv_CNSR.Rd

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

3 changes: 2 additions & 1 deletion man/estimate_KM.Rd

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

1 change: 1 addition & 0 deletions _pkgdown.yml → pkgdown/_pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ reference:
- get_pvalue
- get_quantile
- get_COX_HR
- Surv_CNSR

- title: Data Summary
- contents:
Expand Down
104 changes: 104 additions & 0 deletions tests/testthat/test-Surv_CNSR.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#' @title Specifications test-Surv_CNSR.R
#' @section Last updated by: Steven Haesendonckx
#' @section Last update date: 15JUN2022
#'
#' @section List of tested specifications
#' T1. The function returns a Surv object
#' T1.1 The function returns a Surv object
#' T1.2 The function is compatible with the survival package
#' T1.3 The results of the estimation match between Surv_CNSR and Surv with inverted censoring
#' T2. The function relies on the presence of two numeric variables, specified through AVAL and CNSR, to be present in the envrionment in which they are called
#' T2.1 An error when column name specified through AVAL is not present in the environment
#' T2.2 A warning when the column specified through AVAL has negative values
#' T2.3 A warning when the column specified through AVAL has negative values
#' T2.4 An error when the column name specified through CNSR is not present in the environment
#' T2.5 An error when the column name specified through CNSR in the environment is not numeric

# Requirement T1 ----------------------------------------------------------

testthat::context("Surv_CNSR - T1. The function returns a Surv object")

testthat::test_that("T1.1 The function returns a Surv object", {
testthat::expect_error(surv1 <- with(adtte, visR::Surv_CNSR()), NA)
testthat::expect_error(surv2 <- with(adtte, visR::Surv_CNSR()), NA)
testthat::expect_true(inherits(surv1, "Surv"))
testthat::expect_true(inherits(surv2, "Surv"))
})

testthat::test_that("T1.2 The function is compatible with the survival package", {
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ 1, data = adtte), NA)
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ SEX, data = adtte), NA)

testthat::expect_error(adtte %>% visR::estimate_KM(formula = Surv_CNSR() ~ 1), NA)
testthat::expect_error(adtte %>% visR::estimate_KM(formula = Surv_CNSR() ~ SEX), NA)

testthat::expect_error(survival::survfit(visR::Surv_CNSR(AVAL, CNSR) ~ 1, data = adtte), NA)
testthat::expect_error(survival::survfit(visR::Surv_CNSR(AVAL, CNSR) ~ SEX, data = adtte), NA)

# WHEN THIS TEST FAILS, THAT IS OUR SIGNAL THAT {survival} HAS BEEN UPDATED ON CRAN!!
# AT THAT POINT WE SHOULD DO THE FOLLOWING
# 1. UPDATE THIS UNIT TEST TO ASSURE THERE IS _NO_ ERROR WITH coxph()
# 2. ADD A MIN VERSION REQUIREMENT FOR THE {survival} PACKAGE
# THIS CAN BE DONE IN TWO WAYS:
# 1. in the DESCRIPTION file
# 2. using rlang::check_installed("survival", version = <add required version number>)
testthat::expect_error(survival::coxph(visR::Surv_CNSR() ~ SEX, data = adtte))
})


testthat::test_that("T1.3 The results of the estimation match between Surv_CNSR and Surv with inverted censoring", {
testthat::expect_equal(
with(adtte, visR::Surv_CNSR()),
with(adtte, survival::Surv(AVAL, 1 - CNSR))
)

km1 <- adtte %>% visR::estimate_KM(formula = visR::Surv_CNSR() ~ 1)
km2 <- adtte %>% visR::estimate_KM()
km1$call <- km2$call <- NULL
testthat::expect_equal(km1, km2)

km1 <- adtte %>% visR::estimate_KM(formula = visR::Surv_CNSR() ~ SEX)
km2 <- adtte %>% visR::estimate_KM(strata = "SEX")
km1$call <- km2$call <- NULL
testthat::expect_equal(km1, km2)
})

# Requirement T2 ----------------------------------------------------------

testthat::test_that("T2.1 An error when column name specified through AVAL is not present in the environment", {
testthat::expect_true(! "AVAL" %in% colnames(survival::lung))
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ 1, data = survival::lung))
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ sex, data = survival::lung))

adtte[["AVAL"]] <- NULL
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ 1, data = adtte))
})

testthat::test_that("T2.2 An error when column name specified through AVAL in the environment is not numeric", {
adtte[["AVAL"]] <- as.character(adtte[["AVAL"]])
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ 1, data = adtte))

testthat::expect_error(survival::survfit(visR::Surv_CDISC(AVAL = time) ~ 1, data = survival::lung %>% dplyr::mutate(AVAL = as.character(time))))
})

testthat::test_that("T2.3 A warning when the column specified through AVAL has negative values", {
testthat::expect_warning(survival::survfit(visR::Surv_CNSR() ~ 1, data = adtte %>% dplyr::mutate(AVAL = AVAL - 10000)))
})

testthat::test_that("T2.4 An error when the column name specified through CNSR is not present in the environment", {
testthat::expect_true(! "CNSR" %in% colnames(survival::lung))
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ 1, data = survival::lung %>% dplyr::rename(AVAL = time)))
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ sex, data = survival::lung %>% dplyr::rename(AVAL = time)))

adtte[["CNSR"]] <- NULL
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ 1, data = adtte))
})

testthat::test_that("T2.5 An error when the column name specified through CNSR in the environment is not numeric", {
adtte[["CNSR"]] <- as.character(adtte[["CNSR"]])
testthat::expect_error(survival::survfit(visR::Surv_CNSR() ~ 1, data = adtte))

testthat::expect_error(survival::survfit(visR::Surv_CDISC(AVAL = time) ~ 1, data = survival::lung %>% dplyr::mutate(CNSR = as.character(status))))
})

# END OF CODE -------------------------------------------------------------

0 comments on commit d6a3c6c

Please sign in to comment.