From f92a275a41372d8279c53aa3be414e80c60e9e09 Mon Sep 17 00:00:00 2001 From: Craig Gower-Page Date: Sun, 16 Jun 2024 19:26:20 +0100 Subject: [PATCH] Implement `GridEvent()` (#353) --- DESCRIPTION | 1 + NAMESPACE | 4 +++ R/Grid.R | 3 ++ R/GridEvent.R | 54 ++++++++++++++++++++++++++++++++++ man/Grid-Dev.Rd | 8 ++++-- man/Grid-Functions.Rd | 11 +++++-- man/Quant-Dev.Rd | 13 +++++++-- tests/testthat/test-Grid.R | 59 +++++++++++++++++++++++++++++++++++++- 8 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 R/GridEvent.R diff --git a/DESCRIPTION b/DESCRIPTION index 4de23291a..92c0b7fa3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -71,6 +71,7 @@ Collate: 'DataJoint.R' 'Grid.R' 'GridEven.R' + 'GridEvent.R' 'GridFixed.R' 'GridGrouped.R' 'GridManual.R' diff --git a/NAMESPACE b/NAMESPACE index d214f054e..a6270a432 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ S3method(as.CmdStanMCMC,JointModelSamples) S3method(as.QuantityCollapser,GridEven) +S3method(as.QuantityCollapser,GridEvent) S3method(as.QuantityCollapser,GridFixed) S3method(as.QuantityCollapser,GridGrouped) S3method(as.QuantityCollapser,GridManual) @@ -9,6 +10,7 @@ S3method(as.QuantityCollapser,GridObserved) S3method(as.QuantityCollapser,GridPopulation) S3method(as.QuantityCollapser,GridPrediction) S3method(as.QuantityGenerator,GridEven) +S3method(as.QuantityGenerator,GridEvent) S3method(as.QuantityGenerator,GridFixed) S3method(as.QuantityGenerator,GridGrouped) S3method(as.QuantityGenerator,GridManual) @@ -37,6 +39,7 @@ S3method(as.list,DataLongitudinal) S3method(as.list,DataSubject) S3method(as.list,DataSurvival) S3method(as.list,GridEven) +S3method(as.list,GridEvent) S3method(as.list,GridFixed) S3method(as.list,GridGrouped) S3method(as.list,GridManual) @@ -152,6 +155,7 @@ export(DataLongitudinal) export(DataSubject) export(DataSurvival) export(GridEven) +export(GridEvent) export(GridFixed) export(GridGrouped) export(GridManual) diff --git a/R/Grid.R b/R/Grid.R index 053561eb5..d76b6eb4b 100644 --- a/R/Grid.R +++ b/R/Grid.R @@ -42,6 +42,9 @@ #' - `GridEven()` generates quantities for each subject at N evenly spaced timepoints #' between each subjects first and last longitudinal observations. #' +#' - `GridEvent()` generates one quantity for each subject at their event/censor time +#' as indicated by the `time` variable in the survival dataset. +#' #' - `GridPopulation()` generates longitudinal model quantities based on the population parameters at the #' specified time points. Generates 1 set of quantities for each distinct combination of `arm` #' and `study` within the [`DataSubject`] object provided to the [`JointModel`]. diff --git a/R/GridEvent.R b/R/GridEvent.R new file mode 100644 index 000000000..6057a8da7 --- /dev/null +++ b/R/GridEvent.R @@ -0,0 +1,54 @@ +#' @include Grid.R +#' @include generics.R +NULL + +#' @rdname Grid-Dev +.GridEvent <- setClass( + "GridEvent", + contains = "Grid", + slots = c( + "subjects" = "character_or_NULL" + ) +) + + +#' @rdname Grid-Functions +#' @export +GridEvent <- function(subjects = NULL) { + .GridEvent( + subjects = subjects + ) +} + +#' @rdname Quant-Dev +#' @export +as.QuantityGenerator.GridEvent <- function(object, data, ...) { + assert_class(data, "DataJoint") + assert_that( + !is.null(data@survival), + msg = "Survival data must have been provided to `DataJoint()` in order to use `GridEvent()`" + ) + data_list <- as.list(data) + subjects <- unlist(as.list(object, data = data), use.names = FALSE) + event_times <- data_list$event_times[data_list$subject_to_index[subjects]] + QuantityGeneratorSubject( + times = event_times, + subjects = subjects + ) +} + +#' @rdname Quant-Dev +#' @export +as.QuantityCollapser.GridEvent <- function(object, data, ...) { + generator <- as.QuantityGenerator(object, data) + QuantityCollapser( + times = generator@times, + groups = generator@subjects, + indexes = as.list(seq_along(generator@times)) + ) +} + +#' @export +as.list.GridEvent <- function(x, data, ...) { + subjects_to_list(x@subjects, data) +} diff --git a/man/Grid-Dev.Rd b/man/Grid-Dev.Rd index a11daa568..1146eb01a 100644 --- a/man/Grid-Dev.Rd +++ b/man/Grid-Dev.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/Grid.R, R/GridEven.R, R/GridFixed.R, -% R/GridGrouped.R, R/GridManual.R, R/GridObserved.R, R/GridPopulation.R, -% R/GridPrediction.R +% Please edit documentation in R/Grid.R, R/GridEven.R, R/GridEvent.R, +% R/GridFixed.R, R/GridGrouped.R, R/GridManual.R, R/GridObserved.R, +% R/GridPopulation.R, R/GridPrediction.R \docType{class} \name{Grid-Dev} \alias{Grid-Dev} @@ -9,6 +9,8 @@ \alias{.Grid} \alias{GridEven-class} \alias{.GridEven} +\alias{GridEvent-class} +\alias{.GridEvent} \alias{GridFixed-class} \alias{.GridFixed} \alias{GridGrouped-class} diff --git a/man/Grid-Functions.Rd b/man/Grid-Functions.Rd index 137bd8b64..361c58fda 100644 --- a/man/Grid-Functions.Rd +++ b/man/Grid-Functions.Rd @@ -1,10 +1,11 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/Grid.R, R/GridEven.R, R/GridFixed.R, -% R/GridGrouped.R, R/GridManual.R, R/GridObserved.R, R/GridPopulation.R, -% R/GridPrediction.R +% Please edit documentation in R/Grid.R, R/GridEven.R, R/GridEvent.R, +% R/GridFixed.R, R/GridGrouped.R, R/GridManual.R, R/GridObserved.R, +% R/GridPopulation.R, R/GridPrediction.R \name{Grid-Functions} \alias{Grid-Functions} \alias{GridEven} +\alias{GridEvent} \alias{GridFixed} \alias{GridGrouped} \alias{GridManual} @@ -15,6 +16,8 @@ \usage{ GridEven(subjects = NULL, length.out = 30) +GridEvent(subjects = NULL) + GridFixed(subjects = NULL, times = NULL) GridGrouped(groups, times = NULL) @@ -64,6 +67,8 @@ subject. \item \code{GridManual()} allows for individual timepoint specification for each subject. \item \code{GridEven()} generates quantities for each subject at N evenly spaced timepoints between each subjects first and last longitudinal observations. +\item \code{GridEvent()} generates one quantity for each subject at their event/censor time +as indicated by the \code{time} variable in the survival dataset. \item \code{GridPopulation()} generates longitudinal model quantities based on the population parameters at the specified time points. Generates 1 set of quantities for each distinct combination of \code{arm} and \code{study} within the \code{\link{DataSubject}} object provided to the \code{\link{JointModel}}. diff --git a/man/Quant-Dev.Rd b/man/Quant-Dev.Rd index 307bc600c..f8703ee8c 100644 --- a/man/Quant-Dev.Rd +++ b/man/Quant-Dev.Rd @@ -1,8 +1,9 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/generics.R, R/Grid.R, R/GridEven.R, -% R/GridFixed.R, R/GridGrouped.R, R/GridManual.R, R/GridObserved.R, -% R/GridPopulation.R, R/GridPrediction.R, R/QuantityGeneratorPopulation.R, -% R/QuantityGeneratorPrediction.R, R/QuantityGeneratorSubject.R +% R/GridEvent.R, R/GridFixed.R, R/GridGrouped.R, R/GridManual.R, +% R/GridObserved.R, R/GridPopulation.R, R/GridPrediction.R, +% R/QuantityGeneratorPopulation.R, R/QuantityGeneratorPrediction.R, +% R/QuantityGeneratorSubject.R \docType{class} \name{as.QuantityGenerator} \alias{as.QuantityGenerator} @@ -15,6 +16,8 @@ \alias{QuantityCollapser} \alias{as.QuantityGenerator.GridEven} \alias{as.QuantityCollapser.GridEven} +\alias{as.QuantityGenerator.GridEvent} +\alias{as.QuantityCollapser.GridEvent} \alias{as.QuantityGenerator.GridFixed} \alias{as.QuantityCollapser.GridFixed} \alias{as.QuantityGenerator.GridGrouped} @@ -48,6 +51,10 @@ QuantityCollapser(times, groups, indexes) \method{as.QuantityCollapser}{GridEven}(object, data, ...) +\method{as.QuantityGenerator}{GridEvent}(object, data, ...) + +\method{as.QuantityCollapser}{GridEvent}(object, data, ...) + \method{as.QuantityGenerator}{GridFixed}(object, data, ...) \method{as.QuantityCollapser}{GridFixed}(object, data, ...) diff --git a/tests/testthat/test-Grid.R b/tests/testthat/test-Grid.R index ffd0b7f7b..e3c738287 100644 --- a/tests/testthat/test-Grid.R +++ b/tests/testthat/test-Grid.R @@ -208,7 +208,7 @@ test_that("Grid objects work with QuantityGenerator and QuantityCollapser", { subject = c("A", "B", "C", "D"), arm = c("Arm-A", "Arm-A", "Arm-B", "Arm-B"), study = c("Study-1", "Study-1", "Study-1", "Study-1"), - time = c(1, 2, 3, 4), + time = c(110, 220, 42, 302), event = c(1, 1, 0, 1) ) @@ -357,6 +357,63 @@ test_that("Grid objects work with QuantityGenerator and QuantityCollapser", { ) expect_equal(actual, expected) + + # + # GridEvent + # + grid <- GridEvent( + subjects = c("D", "A", "B") + ) + # Simple comparisons against an identical grid manual + grid_man <- GridManual( + spec = list( + "D" = 302, + "A" = 110, + "B" = 220 + ) + ) + actual <- as.QuantityGenerator(grid, data = dj) + expected <- QuantityGeneratorSubject( + subjects = c("D", "A", "B"), + times = c(302, 110, 220) + ) + expect_equal(actual, expected) + expect_equal( + actual, + as.QuantityGenerator(grid_man, data = dj) + ) + + actual <- as.QuantityCollapser(grid, data = dj) + expected <- QuantityCollapser( + groups = expected@subjects, + times = expected@times, + indexes = list(1, 2, 3) + ) + expect_equal(actual, expected) + expect_equal( + actual, + as.QuantityCollapser(grid_man, data = dj) + ) + + # Check that GridEvent errors if no survival data has been provided + dj2 <- DataJoint( + subject = DataSubject( + data = dat_os, + subject = "subject", + arm = "arm", + study = "study" + ), + longitudinal = DataLongitudinal( + data = dat_lm, + formula = sld ~ time, + threshold = 5 + ) + ) + expect_error( + as.QuantityGenerator(grid, data = dj2), + regexp = "`GridEvent\\(\\)`" + ) + })