diff --git a/DESCRIPTION b/DESCRIPTION index 25496c02..9c44fd8a 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -17,13 +17,13 @@ Description: Implements joint models combining a non-linear mixed effects model In phase 1 studies patients are followed until progression only. Thus, reliable overall survival data and hence estimates are not available. However, we can use additional information from previous clinical trials - or real-world data - we can correlate the tumor response data, that are + or real-world data - we can correlate the tumour response data, that are longitudinal measurements, with the overall survival of the patients or their hazard ratios. Thereby we can predict the overall survival from our phase 1 study data and therefore make better decisions. License: Apache License (>= 2) Encoding: UTF-8 -Language: en-US +Language: en-GB Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.1 Depends: @@ -70,15 +70,13 @@ Collate: 'ParameterList.R' 'StanModel.R' 'LongitudinalModel.R' + 'LinkComponent.R' 'Link.R' 'SurvivalModel.R' 'JointModel.R' 'Quantities.R' 'SurvivalQuantities.R' 'JointModelSamples.R' - 'LinkGSF.R' - 'LinkNone.R' - 'LinkRandomSlope.R' 'LongitudinalGSF.R' 'LongitudinalQuantities.R' 'LongitudinalRandomSlope.R' diff --git a/NAMESPACE b/NAMESPACE index d0166622..0e82c450 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,8 @@ # Generated by roxygen2: do not edit by hand +S3method(as.StanModule,JointModel) +S3method(as.StanModule,Link) +S3method(as.StanModule,LinkComponent) S3method(as.StanModule,Parameter) S3method(as.StanModule,ParameterList) S3method(as.StanModule,Prior) @@ -17,6 +20,8 @@ S3method(as.list,DataJoint) S3method(as.list,DataLongitudinal) S3method(as.list,DataSubject) S3method(as.list,DataSurvival) +S3method(as.list,Link) +S3method(as.list,LinkComponent) S3method(as.list,ParameterList) S3method(as.list,StanModel) S3method(as.list,StanModule) @@ -25,7 +30,7 @@ S3method(as_print_string,DataSubject) S3method(as_print_string,DataSurvival) S3method(as_print_string,JointModelSamples) S3method(as_print_string,Link) -S3method(as_print_string,LinkNone) +S3method(as_print_string,LinkComponent) S3method(as_print_string,LongitudinalModel) S3method(as_print_string,LongitudinalQuantities) S3method(as_print_string,ParameterList) @@ -47,16 +52,29 @@ S3method(autoplot,SurvivalQuantities) S3method(brierScore,SurvivalQuantities) S3method(compileStanModel,JointModel) S3method(dim,Quantities) +S3method(enableLink,LongitudinalGSF) +S3method(enableLink,LongitudinalRandomSlope) S3method(extractVariableNames,DataSubject) S3method(extractVariableNames,DataSurvival) S3method(generateQuantities,JointModelSamples) -S3method(getParameters,default) +S3method(getParameters,Link) +S3method(getParameters,LinkComponent) S3method(initialValues,JointModel) S3method(initialValues,Link) +S3method(initialValues,LinkComponent) S3method(initialValues,Parameter) S3method(initialValues,ParameterList) S3method(initialValues,Prior) S3method(initialValues,StanModel) +S3method(length,Link) +S3method(linkDSLD,LongitudinalGSF) +S3method(linkDSLD,LongitudinalModel) +S3method(linkDSLD,LongitudinalRandomSlope) +S3method(linkIdentity,LongitudinalGSF) +S3method(linkIdentity,LongitudinalModel) +S3method(linkIdentity,LongitudinalRandomSlope) +S3method(linkTTG,LongitudinalGSF) +S3method(linkTTG,LongitudinalModel) S3method(names,Parameter) S3method(names,ParameterList) S3method(sampleStanModel,JointModel) @@ -74,9 +92,7 @@ export(DataSubject) export(DataSurvival) export(JointModel) export(Link) -export(LinkGSF) -export(LinkNone) -export(LinkRandomSlope) +export(LinkComponent) export(LongitudinalGSF) export(LongitudinalModel) export(LongitudinalQuantities) @@ -93,20 +109,23 @@ export(SurvivalLogLogistic) export(SurvivalModel) export(SurvivalQuantities) export(SurvivalWeibullPH) -export(addLink) export(as_stan_list) export(autoplot) export(brierScore) export(compileStanModel) +export(enableLink) export(generateQuantities) export(gsf_dsld) export(gsf_sld) export(gsf_ttg) export(initialValues) -export(link_gsf_abstract) -export(link_gsf_dsld) -export(link_gsf_identity) -export(link_gsf_ttg) +export(linkDSLD) +export(linkIdentity) +export(linkTTG) +export(link_dsld) +export(link_identity) +export(link_none) +export(link_ttg) export(merge) export(prior_beta) export(prior_cauchy) @@ -135,9 +154,6 @@ exportClasses(DataSurvival) exportClasses(JointModel) exportClasses(JointModelSamples) exportClasses(Link) -exportClasses(LinkGSF) -exportClasses(LinkNone) -exportClasses(LinkRandomSlope) exportClasses(LongitudinalGSF) exportClasses(LongitudinalModel) exportClasses(LongitudinalRandomSlope) @@ -150,10 +166,6 @@ exportClasses(SurvivalExponential) exportClasses(SurvivalLogLogistic) exportClasses(SurvivalModel) exportClasses(SurvivalWeibullPH) -exportClasses(link_gsf_abstract) -exportClasses(link_gsf_dsld) -exportClasses(link_gsf_identity) -exportClasses(link_gsf_ttg) exportMethods(show) import(assertthat) import(checkmate) diff --git a/R/DataJoint.R b/R/DataJoint.R index 254ee8dc..198a9bc2 100755 --- a/R/DataJoint.R +++ b/R/DataJoint.R @@ -149,7 +149,7 @@ as_stan_list.DataJoint <- function(object, ...) { )) } -#' @name as_stan_list.DataObject +#' @rdname as_stan_list.DataObject #' @export as.list.DataJoint <- function(x, ...) { as_stan_list(x, ...) diff --git a/R/DataLongitudinal.R b/R/DataLongitudinal.R index d68f1caa..13d79913 100644 --- a/R/DataLongitudinal.R +++ b/R/DataLongitudinal.R @@ -190,7 +190,7 @@ as_stan_list.DataLongitudinal <- function(object, subject_var, ...) { model_data <- list( Nta_total = nrow(df), - # Number of individuals and tumor assessments. + # Number of individuals and tumour assessments. Nta_obs_y = length(index_obs), Nta_cens_y = length(index_cen), @@ -204,7 +204,7 @@ as_stan_list.DataLongitudinal <- function(object, subject_var, ...) { Ythreshold = adj_threshold, # Sparse matrix parameters - # Matrix of individuals x observed tumor assessments. + # Matrix of individuals x observed tumour assessments. n_mat_inds_obs_y = c( length(sparse_mat_inds_obs_y$w), length(sparse_mat_inds_obs_y$v), @@ -214,7 +214,7 @@ as_stan_list.DataLongitudinal <- function(object, subject_var, ...) { v_mat_inds_obs_y = sparse_mat_inds_obs_y$v, u_mat_inds_obs_y = sparse_mat_inds_obs_y$u, - # Matrix of individuals x censored tumor assessments. + # Matrix of individuals x censored tumour assessments. n_mat_inds_cens_y = c( length(sparse_mat_inds_cens_y$w), length(sparse_mat_inds_cens_y$v), diff --git a/R/JointModel.R b/R/JointModel.R index 61386db6..a2c43a8e 100755 --- a/R/JointModel.R +++ b/R/JointModel.R @@ -23,7 +23,6 @@ NULL NULL setClassUnion("LongitudinalModel_OR_NULL", c("LongitudinalModel", "NULL")) -setClassUnion("Link_OR_NULL", c("Link", "NULL")) setClassUnion("SurvivalModel_OR_NULL", c("SurvivalModel", "NULL")) # JointModel-class ---- @@ -44,7 +43,7 @@ setClassUnion("SurvivalModel_OR_NULL", c("SurvivalModel", "NULL")) slots = list( longitudinal = "LongitudinalModel_OR_NULL", survival = "SurvivalModel_OR_NULL", - link = "Link_OR_NULL", + link = "Link", stan = "StanModule", parameters = "ParameterList" ) @@ -57,26 +56,40 @@ setClassUnion("SurvivalModel_OR_NULL", c("SurvivalModel", "NULL")) JointModel <- function( longitudinal = NULL, survival = NULL, - link = NULL + link = link_none() ) { - longitudinal_linked <- addLink(longitudinal, link) - parameters <- merge( - getParameters(longitudinal_linked), - getParameters(survival) + # Ensure that it is a link object (e.g. wrap link components in a Link object) + link <- Link(link) + + if (length(link) > 0) { + longitudinal <- enableLink(longitudinal) + } + + parameters <- Reduce( + merge, + list( + getParameters(longitudinal), + getParameters(survival), + getParameters(link) + ) ) base_model <- paste0(read_stan("base/base.stan"), collapse = "\n") stan_full <- decorated_render( .x = base_model, - longitudinal = add_missing_stan_blocks(as.list(longitudinal_linked)), + longitudinal = add_missing_stan_blocks(as.list(longitudinal)), survival = add_missing_stan_blocks(as.list(survival)), - priors = as.list(parameters), - link_none = class(link)[[1]] == "LinkNone" | is.null(link) + link = add_missing_stan_blocks(as.list(link, model = longitudinal)), + priors = add_missing_stan_blocks(as.list(parameters)) ) - # Resolve any lingering references from longitudinal / survival code - # that haven't yet been rendered + # Unresolved Jinja code within the longitudinal / Survival / Link + # models won't be resolved by the above call to `decorated_render`. + # Instead they it will just be inserted into the template asis. Thus + # we run `decorated_render` again to resolve any lingering Jinja code + # Main example being models that don't have any Jinja code but still + # use the `decorated_render` constants `machine_double_eps`. stan_full <- decorated_render(.x = stan_full) stan_complete <- merge( @@ -94,6 +107,7 @@ JointModel <- function( } + #' `JointModel` -> `character` #' #' Renders a [`JointModel`] object to a stan program @@ -106,6 +120,20 @@ as.character.JointModel <- function(x, ...) { } +#' `JointModel` -> `StanModule` +#' +#' Converts a [`JointModel`] object to a [`StanModule`] object +#' +#' @inheritParams JointModel-Shared +#' +#' @family JointModel +#' @family as.StanModule +#' @export +as.StanModule.JointModel <- function(object, ...) { + object@stan +} + + # write_stan-JointModel ---- #' @rdname write_stan @@ -266,11 +294,7 @@ setMethod( as_print_string(object@longitudinal) |> pad_with_white_space() } - link_string <- if (is.null(object@link) || inherits(object@link, "LinkNone")) { - "\n No Link\n" - } else { - as_print_string(object@link) |> pad_with_white_space() - } + link_string <- as_print_string(object@link) |> pad_with_white_space() string <- "\nA Joint Model with:\n\n Survival:%s\n Longitudinal:%s\n Link:%s\n" cat(sprintf( diff --git a/R/Link.R b/R/Link.R index e85ecd4c..bb0b73e8 100755 --- a/R/Link.R +++ b/R/Link.R @@ -1,85 +1,197 @@ #' @include StanModule.R #' @include LongitudinalModel.R #' @include ParameterList.R +#' @include LinkComponent.R NULL -# Link-class ---- + +#' `Link` Function Arguments +#' +#' This exists just to contain all the common arguments for [`Link`] methods. +#' +#' @param x ([`Link`])\cr a link object. +#' @param object ([`Link`])\cr a link object. +#' @param ... Not Used. +#' +#' @name Link-Shared +#' @keywords internal +NULL + + + #' `Link` #' -#' @slot stan (`StanModule`)\cr code containing the link specification. -#' @slot parameters (`ParameterList`)\cr the parameter specification. -#' @slot name (`character`)\cr display name for the link object. +#' @slot components (`list`)\cr a list of [`LinkComponent`] objects. #' +#' @param ... ([`LinkComponent`])\cr an arbitrary number of link components. +#' +#' @description +#' Simple container class to enable the use of multiple link components in a joint model. +#' Note that the constructor of this object is idempotent e.g. `Link(Link(x)) == Link(x)` +#' +#' @examples +#' Link( +#' link_dsld(), +#' link_ttg() +#' ) +#' +#' @family Link +#' @name Link-class #' @exportClass Link .Link <- setClass( Class = "Link", slots = list( - "name" = "character", - "stan" = "StanModule", - "parameters" = "ParameterList" + components = "list" ) ) -# Link-constructors ---- - #' @rdname Link-class +#' @export +Link <- function(...) { + components <- list(...) + + # Enable copy constructor e.g. if passed a Link just return the Link + if (length(components) == 1 && is(components[[1]], "Link")) { + return(components[[1]]) + } + .Link(components = components) +} + + + +setValidity( + Class = "Link", + method = function(object) { + if (length(object@components) == 0) { + return(TRUE) + } + for (component in object@components) { + if (!is(component, "LinkComponent")) { + return("Link components must be of class `LinkComponent`.") + } + } + return(TRUE) + } +) + + + + +#' `Link` -> `StanModule` #' -#' @inheritParams stanmodel_arguments -#' @param ... additional arguments passed to the constructor. +#' Converts a [`Link`] object to a [`StanModule`] object #' +#' @inheritParams Link-Shared +#' +#' @family Link +#' @family as.StanModule #' @export -Link <- function( - stan = StanModule(), - parameters = ParameterList(), - name = "", - ... -) { - .Link( - name = name, - stan = stan, - parameters = parameters, +as.StanModule.Link <- function(object, ...) { + + if (length(object@components) == 0) { + return(StanModule("base/link_none.stan")) + } + + keys <- vapply( + object@components, + function(x) x@key, + character(1) + ) + + base_stan <- StanModule( + decorated_render( + .x = paste(read_stan("base/link.stan"), collapse = "\n"), + items = as.list(keys) + ) + ) + + stan_list <- lapply( + object@components, + as.StanModule, ... ) + + stan <- Reduce( + merge, + append(base_stan, stan_list) + ) + return(stan) } -# addLink-LongitudinalModel,Link ---- -#' @rdname addLink -setMethod( - f = "addLink", - signature = c("LongitudinalModel", "Link"), - definition = function(x, y, ...) { - x@stan <- merge(x@stan, y@stan) - x@parameters <- merge(x@parameters, y@parameters) - x - } -) -# initialValues-Link ---- -#' @rdname initialValues +#' `Link` -> `list` +#' +#' @inheritParams Link-Shared +#' +#' @description +#' Returns a named list where each element of the list corresponds +#' to a Stan modelling block e.g. `data`, `model`, etc. +#' +#' @family Link #' @export -initialValues.Link <- function(object, n_chains, ...) { - initialValues(object@parameters, n_chains) +as.list.Link <- function(x, ...) { + as.list(as.StanModule(x, ...)) } -# Link-as.StanModule ---- -#' @rdname as.StanModule -as.StanModule.Link <- function(object) { - object@stan +#' @export +#' @rdname getParameters +getParameters.Link <- function(object, ...) { + parameters_list <- lapply( + object@components, + getParameters, + ... + ) + Reduce( + merge, + parameters_list + ) } + +#' @rdname initialValues +#' @export +initialValues.Link <- function(object, ...) { + unlist( + lapply(object@components, initialValues), + recursive = FALSE + ) +} + + +#' `Link` -> `list` +#' +#' @inheritParams Link-Shared +#' +#' @description +#' Returns the number of link components within the [`Link`] object +#' +#' @family Link +#' @export +length.Link <- function(x) { + length(x@components) +} + + #' @export as_print_string.Link <- function(object, ...) { - string <- sprintf( - "\n%s Link with parameters:\n%s\n\n", - object@name, - paste(" ", as_print_string(object@parameters)) |> paste(collapse = "\n") + if (length(object) == 0) { + return("\nNo Link") + } + paste( + c( + "\nLink with the following components/parameters:", + paste0( + " ", + vapply(object@components, as_print_string, character(1)) + ) + ), + collapse = "\n" ) - return(string) } @@ -89,6 +201,6 @@ setMethod( f = "show", signature = "Link", definition = function(object) { - cat(as_print_string(object)) + cat(paste0(as_print_string(object), "\n")) } ) diff --git a/R/LinkComponent.R b/R/LinkComponent.R new file mode 100644 index 00000000..fc555024 --- /dev/null +++ b/R/LinkComponent.R @@ -0,0 +1,227 @@ +#' @include StanModule.R +#' @include LongitudinalModel.R +#' @include ParameterList.R +#' @include generics.R +NULL + + +setClassUnion("StanModule_or_Function", c("StanModule", "function")) + + + +#' `LinkComponent` Function Arguments +#' +#' This exists just to contain all the common arguments for [`LinkComponent`] methods. +#' +#' @param stan (`StanModule`)\cr Stan code. +#' @param x ([`LinkComponent`])\cr a link component. +#' @param object ([`LinkComponent`])\cr a link component. +#' @param ... Not Used. +#' +#' @name LinkComponent-Shared +#' @keywords internal +NULL + + + + +#' `LinkComponent` +#' +#' @slot stan (`StanModule`)\cr See Arguments. +#' @slot parameters (`ParameterList`)\cr See Arguments. +#' @slot name (`character`)\cr See Arguments. +#' +#' @param stan (`StanModule` or `function`)\cr Stan code. See Details. +#' @param parameters (`ParameterList`)\cr The parameter specification. +#' @param key (`character`)\cr Link identifier. See Details. +#' +#' @details +#' +#' This object provides key information needed to construct a link contribution in the +#' survival model based on the parameters of the longitudinal model. +#' +#' Each link component defines a stan function of the longitudinal model parameters which is +#' multiplied by a model coefficient and added to the survival models hazard function. +#' +#' For full details about the specification of a `LinkComponent` please see +#' \code{vignette("extending-jmpost", package = "jmpost")}. +#' +#' The `stan` argument can be either a `StanModule` object or a function. +#' If a function is provided, it must take a single argument, a +#' `LongitudinalModel` object, and return a `StanModule` object. This allows for +#' generic functions to be used for links such as `dsld` which allows for each model +#' to provide their own model specific implementation. +#' +#' @family LinkComponent +#' @name LinkComponent-class +#' @exportClass Link +.LinkComponent <- setClass( + Class = "LinkComponent", + slots = list( + "stan" = "StanModule_or_Function", + "parameters" = "ParameterList", + "key" = "character" + ) +) + + +#' @rdname LinkComponent-class +#' @inheritParams stanmodel_arguments +#' @export +LinkComponent <- function( + stan, + parameters = ParameterList(), + key = "", + ... +) { + .LinkComponent( + stan = stan, + key = key, + parameters = parameters, + ... + ) +} + + + + +#' @family LinkComponent +#' @rdname getParameters +#' @export +getParameters.LinkComponent <- function(object, ...) { + object@parameters +} + + +#' @family LinkComponent +#' @rdname initialValues +#' @export +initialValues.LinkComponent <- function(object, n_chains, ...) { + initialValues(object@parameters, n_chains) +} + + + + +#' `LinkComponent` -> `StanModule` +#' +#' Converts a [`LinkComponent`] object to a [`StanModule`] object +#' +#' @inheritParams LinkComponent-Shared +#' @param model (`LongitudinalModel`)\cr The longitudinal model. +#' +#' @family LinkComponent +#' @family as.StanModule +#' @export +as.StanModule.LinkComponent <- function(object, model = NULL, ...) { + if (is(object@stan, "StanModule")) { + return(object@stan) + } + if (is.function(object@stan)) { + assert_that( + is(model, "LongitudinalModel"), + msg = "`model` must be a LongitudinalModel object" + ) + stan <- object@stan(model) + assert_that( + is(stan, "StanModule"), + msg = "The function must return a StanModule object" + ) + return(stan) + } + stop("Something went wrong") +} + + + +#' `LinkComponent` -> `list` +#' +#' @inheritParams LinkComponent-Shared +#' +#' @description +#' Returns a named list where each element of the list corresponds +#' to a Stan modelling block e.g. `data`, `model`, etc. +#' +#' @family LinkComponent +#' @export +as.list.LinkComponent <- function(x, ...) { + stan <- as.StanModule(x, ...) + as.list(stan) +} + + +#' @export +as_print_string.LinkComponent <- function(object, ...) { + as_print_string(object@parameters) +} + + + +#' Standard Links +#' +#' @description +#' +#' These functions enable the inclusion of several common link functions in the survival model of +#' the joint model. +#' +#' Note that the underlying implementation of these links is specific to each longitudinal model. +#' +#' @param prior (`Prior`)\cr The prior to use for the corresponding link coeficient. +#' @name standard-links +NULL + + +#' @describeIn standard-links Time to growth link +#' @export +link_ttg <- function(prior = prior_normal(0, 2)) { + LinkComponent( + key = "link_ttg", + stan = linkTTG, + parameters = ParameterList(Parameter(name = "link_ttg", prior = prior, size = 1)) + ) +} + + +#' @describeIn standard-links Derivative of the SLD over time link +#' @export +link_dsld <- function(prior = prior_normal(0, 2)) { + LinkComponent( + key = "link_dsld", + stan = linkDSLD, + parameters = ParameterList(Parameter(name = "link_dsld", prior = prior, size = 1)) + ) +} + + +#' @describeIn standard-links Current SLD link +#' @export +link_identity <- function(prior = prior_normal(0, 2)) { + LinkComponent( + key = "link_identity", + stan = linkIdentity, + parameters = ParameterList(Parameter(name = "link_identity", prior = prior, size = 1)) + ) +} + + +#' @describeIn standard-links No link (fit the survival and longitudinal models independently) +#' @export +link_none <- function() { + Link() +} + + +#' @rdname show-object +#' @export +setMethod( + f = "show", + signature = "LinkComponent", + definition = function(object) { + cat( + paste0( + "\nLinkComponent with parameter:\n ", + as_print_string(object), "\n\n" + ) + ) + } +) diff --git a/R/LinkGSF.R b/R/LinkGSF.R deleted file mode 100644 index 5a154eb7..00000000 --- a/R/LinkGSF.R +++ /dev/null @@ -1,226 +0,0 @@ -#' @include Link.R -NULL - -# LinkGSF-class ---- - -#' `LinkGSF` -#' -#' This class extends the general [`Link`][Link-class] class for -#' the [`LongitudinalGSF`][LongitudinalGSF-class] -#' model. -#' -#' @exportClass LinkGSF -.LinkGSF <- setClass( - Class = "LinkGSF", - contains = "Link" -) - -# LinkGSF-constructors ---- - -#' @rdname LinkGSF-class -#' -#' @param ... (`link_gsf_abstract`)\cr which link components should be included. If no arguments -#' are provided then this will be set to [link_gsf_dsld()] and [link_gsf_ttg()] -#' -#' @export -LinkGSF <- function(...) { - components <- list(...) - if (!length(components)) { - components <- list( - link_gsf_dsld(), - link_gsf_ttg() - ) - } - - items <- lapply( - components, - function(x) { - list( - parameter = x@parameter_name, - contribution_function = x@contribution_fname - ) - } - ) - - rendered_link <- decorated_render( - .x = paste0(read_stan("lm-gsf/link.stan"), collapse = "\n"), - items = items - ) - - parameters <- ParameterList() - stan_components <- StanModule() - for (item in components) { - parameters <- merge(parameters, item@parameter) - stan_components <- merge(stan_components, item@stan) - } - - stan_full <- merge( - stan_components, - StanModule(rendered_link) - ) - - names <- vapply(components, \(x) x@name, character(1)) - - x <- Link( - name = sprintf("GSF (%s)", paste0(names, collapse = " + ")), - stan = stan_full, - parameters = parameters - ) - - .LinkGSF(x) -} - -# link_gsf_abstract-class ---- - -#' `link_gsf_abstract` -#' -#' Class to specify the contents for [`LinkGSF`]. -#' -#' @slot stan (`StanModule`)\cr the Stan code. -#' @slot name (`character`)\cr the link name to be displayed. -#' @slot parameter (`ParameterList`)\cr the parameter specification. -#' @slot parameter_name (`character`)\cr the name of the parameter. -#' @slot contribution_fname (`character`)\cr the function name of the contribution. -#' -#' @exportClass link_gsf_abstract -#' @keywords internal -.link_gsf_abstract <- setClass( - Class = "link_gsf_abstract", - slots = list( - "stan" = "StanModule", - "name" = "character", - "parameter" = "ParameterList", - "parameter_name" = "character", - "contribution_fname" = "character" - ) -) - -# link_gsf_abstract-constructors ---- - -#' @rdname link_gsf_abstract-class -#' -#' @inheritParams stanmodel_arguments -#' @param parameter_name (`character`)\cr the name of the parameter. -#' @param contribution_fname (`character`)\cr the function name of the contribution. -#' -#' @note Only the `functions` part of `stan` will be used. -#' -#' @export -link_gsf_abstract <- function( - stan, - parameter, - parameter_name, - contribution_fname, - name -) { - .link_gsf_abstract( - parameter = parameter, - parameter_name = parameter_name, - contribution_fname = contribution_fname, - name = name, - stan = StanModule( - paste0( - "functions {\n", - as.list(stan)[["functions"]], - "\n}" - ) - ) - ) -} - -# link_gsf_ttg-class ---- - -#' `link_gsf_ttg` -#' -#' This class extends the general [`link_gsf_abstract`] for the time-to-growth -#' (`ttg`) link contribution. -#' -#' @exportClass link_gsf_ttg -.link_gsf_ttg <- setClass( - Class = "link_gsf_ttg", - contains = "link_gsf_abstract" -) - -# link_gsf_ttg-constructors ---- - -#' @rdname link_gsf_ttg-class -#' -#' @param gamma (`Prior`)\cr prior for the link coefficient `gamma`. -#' -#' @export -link_gsf_ttg <- function( - gamma = prior_normal(0, 5) -) { - .link_gsf_ttg( - name = "TTG", - stan = StanModule("lm-gsf/link_ttg.stan"), - parameter = ParameterList(Parameter(name = "lm_gsf_gamma", prior = gamma, size = 1)), - parameter_name = "lm_gsf_gamma", - contribution_fname = "link_ttg_contribution" - ) -} - -# link_gsf_dsld-class ---- - -#' `link_gsf_dsld` -#' -#' This class extends the general [`link_gsf_abstract`] for the derivative of the -#' sum of longest diameters (`dsld`) link contribution. -#' -#' @exportClass link_gsf_dsld -.link_gsf_dsld <- setClass( - Class = "link_gsf_dsld", - contains = "link_gsf_abstract" -) - - -# link_gsf_dsld-constructors ---- - -#' @rdname link_gsf_dsld-class -#' -#' @param beta (`Prior`)\cr prior for the link coefficient `beta`. -#' -#' @export -link_gsf_dsld <- function( - beta = prior_normal(0, 5) -) { - .link_gsf_dsld( - name = "dSLD", - stan = StanModule("lm-gsf/link_dsld.stan"), - parameter = ParameterList(Parameter(name = "lm_gsf_beta", prior = beta, size = 1)), - parameter_name = "lm_gsf_beta", - contribution_fname = "link_dsld_contribution" - ) -} - - -# link_gsf_identity-class ---- - -#' `link_gsf_identity` -#' -#' This class extends the general [link_gsf_abstract()] for the identity of the -#' sum of longest diameters (`sld`) link contribution. -#' -#' @exportClass link_gsf_identity -.link_gsf_identity <- setClass( - Class = "link_gsf_identity", - contains = "link_gsf_abstract" -) - - -# link_gsf_identity-constructors ---- - -#' @rdname link_gsf_identity-class -#' -#' @param tau (`Prior`)\cr prior for the link coefficient `tau`. -#' -#' @export -link_gsf_identity <- function(tau = prior_normal(0, 5)) { - .link_gsf_identity( - name = "Identity", - stan = StanModule("lm-gsf/link_identity.stan"), - parameter = ParameterList(Parameter(name = "lm_gsf_tau", prior = tau, size = 1)), - parameter_name = "lm_gsf_tau", - contribution_fname = "link_identity_contribution" - ) -} diff --git a/R/LinkNone.R b/R/LinkNone.R deleted file mode 100755 index 47049c51..00000000 --- a/R/LinkNone.R +++ /dev/null @@ -1,54 +0,0 @@ -#' @include generics.R -#' @include Link.R -#' @include LongitudinalModel.R -NULL - -# LinkNone-class ---- - -#' `LinkNone` -#' -#' This class extends the general [`Link`] class to specify no link -#' between the longitudinal and the time-to-event models. -#' -#' @exportClass LinkNone -.LinkNone <- setClass( - Class = "LinkNone", - contains = "Link" -) - -# LinkNone-constructors ---- - -#' @rdname LinkNone-class -#' -#' @export -LinkNone <- function() { - stan <- StanModule() - .LinkNone(Link(stan = stan, name = "None")) -} - -# addLink-LongitudinalModel,LinkNone ---- - -#' @rdname addLink -setMethod( - f = "addLink", - signature = c("LongitudinalModel", "LinkNone"), - definition = function(x, y, ...) { - x - } -) - - -#' @rdname show-object -#' @export -setMethod( - f = "show", - signature = "LinkNone", - definition = function(object) { - cat(as_print_string(object)) - } -) - -#' @export -as_print_string.LinkNone <- function(object, ...) { - return("No Link\n\n") -} diff --git a/R/LinkRandomSlope.R b/R/LinkRandomSlope.R deleted file mode 100644 index 79c5f24f..00000000 --- a/R/LinkRandomSlope.R +++ /dev/null @@ -1,39 +0,0 @@ -#' @include Link.R -NULL - -# LinkRandomSlope-class ---- - -#' `LinkRandomSlope` -#' -#' This class extends the general [`Link`] class for the [`LongitudinalRandomSlope`] -#' model. -#' -#' @exportClass LinkRandomSlope -.LinkRandomSlope <- setClass( - Class = "LinkRandomSlope", - contains = "Link" -) - -# LinkRandomSlope-constructors ---- - -#' @rdname LinkRandomSlope-class -#' -#' @param link_lm_phi (`Prior`)\cr prior for the link coefficient for the -#' random slope link contribution. -#' -#' @export -LinkRandomSlope <- function( - link_lm_phi = prior_normal(0.2, 0.5) -) { - .LinkRandomSlope( - Link( - name = "Random Slope", - stan = StanModule( - x = "lm-random-slope/links.stan" - ), - parameters = ParameterList( - Parameter(name = "link_lm_phi", prior = link_lm_phi, size = 1) - ) - ) - ) -} diff --git a/R/LongitudinalGSF.R b/R/LongitudinalGSF.R index f7eb59b1..174a6d13 100755 --- a/R/LongitudinalGSF.R +++ b/R/LongitudinalGSF.R @@ -36,7 +36,7 @@ NULL #' @param a_phi (`Prior`)\cr for the alpha parameter for the fraction of cells that respond to treatment. #' @param b_phi (`Prior`)\cr for the beta parameter for the fraction of cells that respond to treatment. #' -#' @param centered (`logical`)\cr whether to use the centered parameterization. +#' @param centred (`logical`)\cr whether to use the centred parameterization. #' #' @export LongitudinalGSF <- function( @@ -54,12 +54,12 @@ LongitudinalGSF <- function( sigma = prior_lognormal(log(0.1), 1), - centered = FALSE + centred = FALSE ) { gsf_model <- StanModule(decorated_render( .x = paste0(read_stan("lm-gsf/model.stan"), collapse = "\n"), - centered = centered + centred = centred )) parameters <- list( @@ -82,8 +82,8 @@ LongitudinalGSF <- function( Parameter(name = "lm_gsf_sigma", prior = sigma, size = 1) ) - assert_flag(centered) - parameters_extra <- if (centered) { + assert_flag(centred) + parameters_extra <- if (centred) { list( Parameter( name = "lm_gsf_psi_bsld", @@ -120,3 +120,32 @@ LongitudinalGSF <- function( ) .LongitudinalGSF(x) } + + +#' @rdname standard-link-methods +#' @export +enableLink.LongitudinalGSF <- function(object, ...) { + object@stan <- merge( + object@stan, + StanModule("lm-gsf/link.stan") + ) + object +} + +#' @rdname standard-link-methods +#' @export +linkDSLD.LongitudinalGSF <- function(object, ...) { + StanModule("lm-gsf/link_dsld.stan") +} + +#' @rdname standard-link-methods +#' @export +linkTTG.LongitudinalGSF <- function(object, ...) { + StanModule("lm-gsf/link_ttg.stan") +} + +#' @rdname standard-link-methods +#' @export +linkIdentity.LongitudinalGSF <- function(object, ...) { + StanModule("lm-gsf/link_identity.stan") +} diff --git a/R/LongitudinalModel.R b/R/LongitudinalModel.R index dda1aad7..46897f7a 100755 --- a/R/LongitudinalModel.R +++ b/R/LongitudinalModel.R @@ -51,3 +51,34 @@ as_print_string.LongitudinalModel <- function(object, ...) { ) return(string) } + + +#' @export +linkTTG.LongitudinalModel <- function(object, ...) { + stop( + sprintf( + "TTG link is not available for the %s model", + object@name + ) + ) +} + +#' @export +linkDSLD.LongitudinalModel <- function(object, ...) { + stop( + sprintf( + "DSLD link is not available for the %s model", + object@name + ) + ) +} + +#' @export +linkIdentity.LongitudinalModel <- function(object, ...) { + stop( + sprintf( + "Identity link is not available for the %s model", + object@name + ) + ) +} diff --git a/R/LongitudinalRandomSlope.R b/R/LongitudinalRandomSlope.R index 70e24340..c9ffae3a 100755 --- a/R/LongitudinalRandomSlope.R +++ b/R/LongitudinalRandomSlope.R @@ -55,3 +55,27 @@ LongitudinalRandomSlope <- function( ) ) } + + +#' @rdname standard-link-methods +#' @export +enableLink.LongitudinalRandomSlope <- function(object, ...) { + object@stan <- merge( + object@stan, + StanModule("lm-random-slope/link.stan") + ) + object +} + +#' @rdname standard-link-methods +#' @export +linkDSLD.LongitudinalRandomSlope <- function(object, ...) { + StanModule("lm-random-slope/link_dsld.stan") +} + + +#' @rdname standard-link-methods +#' @export +linkIdentity.LongitudinalRandomSlope <- function(object, ...) { + StanModule("lm-random-slope/link_identity.stan") +} diff --git a/R/defaults.R b/R/defaults.R index 3ede8bdd..955d179c 100755 --- a/R/defaults.R +++ b/R/defaults.R @@ -7,37 +7,9 @@ #' @include Parameter.R NULL -# addLink ---- -## addLink-LongitudinalModel,NULL ---- -#' @rdname addLink -setMethod( - "addLink", - signature = c("LongitudinalModel", "NULL"), - definition = function(x, y, ...) x -) - -## addLink-NULL,Link ---- - -#' @rdname addLink -setMethod( - "addLink", - signature = c("NULL", "Link"), - definition = function(x, y, ...) stop("LongitudinalModel needs to be defined") -) - -## addLink-NULL,NULL ---- - -#' @rdname addLink -setMethod( - "addLink", - signature = c("NULL", "NULL"), - definition = function(x, y, ...) NULL -) - -# getParameters ---- -#' @export +#' @rdname getParameters getParameters.default <- function(object) { if (missing(object) || is.null(object)) { return(NULL) diff --git a/R/generics.R b/R/generics.R index dcd640a2..e78e5cc9 100755 --- a/R/generics.R +++ b/R/generics.R @@ -35,26 +35,6 @@ setGeneric( NULL - -# addLink ---- - -#' `addLink` -#' -#' Add a link to a longitudinal model. -#' -#' @param x the longitudinal model. -#' @param y the link to be added. -#' @param ... additional arguments. -#' -#' @export -# Needs to be S4 for multiple dispatch ! -setGeneric( - name = "addLink", - def = function(x, y, ...) standardGeneric("addLink") -) - - - # write_stan ---- #' `write_stan` @@ -113,11 +93,11 @@ as.StanModule <- function(object, ...) { } -# getParameters ---- #' `getParameters` #' -#' Obtain the parameters from a [`StanModel`]. +#' Extract any modelling parameters as a [`ParameterList`] object +#' from a model. #' #' @param object where to obtain the parameters from. #' @@ -293,3 +273,57 @@ NULL brierScore <- function(object, ...) { UseMethod("brierScore") } + + + + + + +#' Standard Link Methods +#' +#' @param object ([`StanModel`]) \cr A [`StanModel`] object. +#' @param ... Not used. +#' +#' @description +#' These generic functions enable [`LongitudinalModel`] objects to provide +#' their own implementations for the most common link functions. +#' +#' @details +#' Each of these methods should return a [`StanModule`] argument that implements +#' the models corresponding version of that link type. +#' For `enableLink` this is called once for a model regardless of how many links +#' are used and its purpose is to provide the stan code to initialise any +#' link specific objects (to avoid clashes with each individual link function declaring +#' the same required stan objects). +#' +#' For further details on how to use these methods please see +#' \code{vignette("extending-jmpost", package = "jmpost")}. +#' +#' @name standard-link-methods +NULL + + +#' @describeIn standard-link-methods hook to include any common link code to be shared across all +#' link functions +#' @export +enableLink <- function(object, ...) { + UseMethod("enableLink") +} + +#' @describeIn standard-link-methods Time to growth link +#' @export +linkTTG <- function(object, ...) { + UseMethod("linkTTG") +} + +#' @describeIn standard-link-methods Derivative of the SLD over time link +#' @export +linkDSLD <- function(object, ...) { + UseMethod("linkDSLD") +} + +#' @describeIn standard-link-methods Current SLD link +#' @export +linkIdentity <- function(object, ...) { + UseMethod("linkIdentity") +} diff --git a/R/simulations_rs.R b/R/simulations_rs.R index e845ff6e..d13a7e59 100644 --- a/R/simulations_rs.R +++ b/R/simulations_rs.R @@ -5,7 +5,8 @@ #' @param slope_mu (`numeric`)\cr the population slope for the two treatment arms. #' @param slope_sigma (`number`)\cr the random slope standard deviation. #' @param sigma (`number`)\cr the variance of the longitudinal values. -#' @param phi (`number`)\cr the link coefficient for the random slope contribution. +#' @param link_dsld (`number`)\cr the link coefficient for the DSLD contribution. +#' @param link_identity (`number`)\cr the link coefficient for the identity contribution. #' #' @returns A function with argument `lm_base` that can be used to simulate #' longitudinal data from the corresponding random slope model. @@ -16,7 +17,8 @@ sim_lm_random_slope <- function( slope_mu = c(0.01, 0.03), slope_sigma = 0.5, sigma = 2, - phi = 0.1 + link_dsld = 0, + link_identity = 0 ) { function(lm_base) { @@ -40,7 +42,7 @@ sim_lm_random_slope <- function( dplyr::mutate(err = stats::rnorm(dplyr::n(), 0, sigma)) |> dplyr::left_join(rs_baseline, by = "pt") |> dplyr::mutate(sld = intercept + .data$slope_ind * .data$time + .data$err) |> - dplyr::mutate(log_haz_link = .data$slope_ind * phi) + dplyr::mutate(log_haz_link = .data$slope_ind * link_dsld + .data$sld * link_identity) return(lm_dat) } diff --git a/README.Rmd b/README.Rmd index 3155c1ae..8f975501 100644 --- a/README.Rmd +++ b/README.Rmd @@ -28,10 +28,10 @@ The goal of the `jmpost` package is to fit joint models involving: 1. a nonlinear (or linear) mixed-effect sub-model, describing individual time profiles (_i.e._ trajectories) for a continuous marker, 1. a link function (_a.k.a._ association term). -More specifically, the model implemented in this package utilizes a modeling framework described previously **[1-3]** to link overall survival to tumor size data in oncology clinical trials. +More specifically, the model implemented in this package utilizes a modelling framework described previously **[1-3]** to link overall survival to tumour size data in oncology clinical trials. -**[1]** [Tardivon _et al._ Association between tumor size kinetics and survival in patients with urothelial carcinoma treated with atezolizumab: Implications for patient follow-up. _Clin Pharm Ther_, 2019](https://doi.org/10.1002/cpt.1450). -**[2]** [Kerioui _et al._ Bayesian inference using Hamiltonian Monte-Carlo algorithm for nonlinear joint modeling in the context of cancer immunotherapy. _Stat in Med_, 2020](https://doi.org/10.1002/sim.8756). +**[1]** [Tardivon _et al._ Association between tumour size kinetics and survival in patients with urothelial carcinoma treated with atezolizumab: Implications for patient follow-up. _Clin Pharm Ther_, 2019](https://doi.org/10.1002/cpt.1450). +**[2]** [Kerioui _et al._ Bayesian inference using Hamiltonian Monte-Carlo algorithm for nonlinear joint modelling in the context of cancer immunotherapy. _Stat in Med_, 2020](https://doi.org/10.1002/sim.8756). **[3]** [Kerioui _et al._ Modelling the association between biomarkers and clinical outcome: An introduction to nonlinear joint models. _Br J Clin Pharm_, 2022](https://doi.org/10.1111/bcp.15200). The models are implemented in [STAN](https://mc-stan.org/), and the package provides a flexible user interface. @@ -102,8 +102,11 @@ derivative and another term for the time-to-growth. ```{r model_spec} joint_model <- JointModel( longitudinal = LongitudinalGSF(), - link = LinkGSF(), - survival = SurvivalWeibullPH() + survival = SurvivalWeibullPH(), + link = Link( + link_dsld(), + link_ttg() + ) ) ``` diff --git a/README.md b/README.md index 3f4b9b4a..803fcc01 100755 --- a/README.md +++ b/README.md @@ -22,15 +22,15 @@ The goal of the `jmpost` package is to fit joint models involving: 3. a link function (*a.k.a.* association term). More specifically, the model implemented in this package utilizes a -modeling framework described previously **\[1-3\]** to link overall -survival to tumor size data in oncology clinical trials. +modelling framework described previously **\[1-3\]** to link overall +survival to tumour size data in oncology clinical trials. -**\[1\]** [Tardivon *et al.* Association between tumor size kinetics and +**\[1\]** [Tardivon *et al.* Association between tumour size kinetics and survival in patients with urothelial carcinoma treated with atezolizumab: Implications for patient follow-up. *Clin Pharm Ther*, 2019](https://doi.org/10.1002/cpt.1450). **\[2\]** [Kerioui *et al.* Bayesian inference using Hamiltonian -Monte-Carlo algorithm for nonlinear joint modeling in the context of +Monte-Carlo algorithm for nonlinear joint modelling in the context of cancer immunotherapy. *Stat in Med*, 2020](https://doi.org/10.1002/sim.8756). **\[3\]** [Kerioui *et al.* Modelling the association between biomarkers @@ -75,9 +75,6 @@ right format. ``` r library(jmpost) -#> Registered S3 method overwritten by 'GGally': -#> method from -#> +.gg ggplot2 #> Registered S3 methods overwritten by 'ggpp': #> method from #> heightDetails.titleGrob ggplot2 @@ -118,8 +115,11 @@ via a term for the derivative and another term for the time-to-growth. ``` r joint_model <- JointModel( longitudinal = LongitudinalGSF(), - link = LinkGSF(), - survival = SurvivalWeibullPH() + survival = SurvivalWeibullPH(), + link = Link( + link_dsld(), + link_ttg() + ) ) ``` diff --git a/_pkgdown.yml b/_pkgdown.yml index 65edab30..059bdb8d 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -77,17 +77,16 @@ reference: - title: Link Specification contents: - - link_gsf_dsld - - link_gsf_ttg - - link_gsf_identity + - link_dsld + - link_ttg + - link_identity + - link_none - Link - - LinkGSF - - LinkNone - - LinkRandomSlope + - LinkComponent + - standard-link-methods - title: Joint Model Specification contents: - - addLink - JointModel - title: Joint Model Fitting @@ -131,6 +130,9 @@ reference: - as.StanModule.Parameter - as.StanModule.ParameterList - as.StanModule.Prior + - as.StanModule.JointModel + - as.StanModule.Link + - as.StanModule.LinkComponent - as.character.Parameter - as.character.Prior - show-object @@ -149,6 +151,9 @@ reference: - as.list.ParameterList - as.list.StanModel - as.list.StanModule + - as.list.Link + - as.list.LinkComponent + - length.Link - subset.DataJoint - extractVariableNames.DataLongitudinal diff --git a/design/Intro_to_jmpost.Rmd b/design/Intro_to_jmpost.Rmd index 8b5995da..306c6b20 100755 --- a/design/Intro_to_jmpost.Rmd +++ b/design/Intro_to_jmpost.Rmd @@ -36,7 +36,7 @@ In clinical development of new cancer medicines a key challenge is to decide on In phase 1 studies patients are followed until the cancer is growing again (progression event). Thus, reliable overall survival data are not available. However, we can use additional information from previous clinical trials or real-world data to understand the time between progression and death. -Specifically, we can correlate the tumor response data, that are longitudinal measurements, (i.e. the shrinkage and growth of tumor lesions over time) with the overall survival of the patients or their hazard ratios [@beyer2020multistate]. +Specifically, we can correlate the tumour response data, that are longitudinal measurements, (i.e. the shrinkage and growth of tumour lesions over time) with the overall survival of the patients or their hazard ratios [@beyer2020multistate]. Thereby we can predict the overall survival in our Phase 1 studies and therefore make better decisions [@kerioui2020bayesian]. The sum of the longest diameters (SLD) of the cancer lesions provides an effective and representative biomarker for longitudinal measurements in solid cancer. @@ -44,15 +44,15 @@ SLD is relatively easy to measure with reasonable accuracy while also being a go The association between the longitudinal data and the overall survival can be achieved in different ways. The predicted SLD value or its first derivative over time can be directly treated as the linking factor in the hazard model for survival. -Other factors such as the time to growth, the tumor shrinkage rate or the tumor growth rate can also be used to associate longitudinal outcomes with overall survival. +Other factors such as the time to growth, the tumour shrinkage rate or the tumour growth rate can also be used to associate longitudinal outcomes with overall survival. Bayesian methodology provides an important tool for the analysis of similar data. Firstly, the use of historical data from previous trials is improving the performance of the models. Secondly, prior definition allows the adjustment of the models according to the specific factors of each analysis. Although these statistical methods provide significant advantages in decision making processes in cancer drug development, their implementation is relatively intricate and time consuming. -Compared to other R-packages such as `joineR` and `jmbayes`, `jmpost` provides the flexibility of non-linear mixed effects modeling for the longitudinal part of the joint model. -Non-linear parametrization respects the biological implications of clinical trials in oncology where the observed tumor response usually follows non-linear patterns. +Compared to other R-packages such as `joineR` and `jmbayes`, `jmpost` provides the flexibility of non-linear mixed effects modelling for the longitudinal part of the joint model. +Non-linear parametrization respects the biological implications of clinical trials in oncology where the observed tumour response usually follows non-linear patterns. # Minimal usage @@ -79,7 +79,7 @@ Secondly, the summary report of the model run. # Customizing the analysis `jmpost` is a package aiming to optimize the procedure of developing a new Bayesian joint model. -The package provides complete examples of joint modeling code. +The package provides complete examples of joint modelling code. Here we explain the workflow of the package `jmpost` using `stan` libraries as an example. \begin{figure}[!] @@ -116,7 +116,7 @@ Here we explain the workflow of the package `jmpost` using `stan` libraries as a Figure~1 illustrates the main workflow of the `jmpost` package. Three objects need to be combined for the construction of the full Bayesian joint model. -The longitudinal sub-model, including all the required information for the modeling of the longitudinal measurements, the survival sub-model, including information of for the time to event data and the link or association factor, containing information for the conection of the two sub-models. +The longitudinal sub-model, including all the required information for the modelling of the longitudinal measurements, the survival sub-model, including information of for the time to event data and the link or association factor, containing information for the conection of the two sub-models. ```{r Longitudinal model} Long_mod <- LongModel( @@ -296,7 +296,7 @@ y_{ij} \sim N(SLD_{ij}, SLD^2_{ij}\sigma^2) Where: \begin{itemize} -\item $y_{ij}$ is the observed tumor mesasurements +\item $y_{ij}$ is the observed tumour mesasurements \item $SLD_{ij}$ is the expected sum of longest diameter for subject $i$ at time point $j$ \end{itemize} ### Expected SLD @@ -314,7 +314,7 @@ Where: \item $SLD_{ij}$ is the observed SLD measurement for subject $i$ at visit $j$ \item $b_i$ is the Baseline sld measurement \item $s_i$ is the kinetics shrinkage parameter -\item $g_i$ is the kinetics tumor growth parameter +\item $g_i$ is the kinetics tumour growth parameter \item $\phi_i$ is the proportion of cells affected by the treatment \end{itemize} @@ -405,7 +405,7 @@ Where: \item $G_i$ is the time to growth for subject $i$ \item $b_i$ is the baseline SLD measurement \item $s_i$ is the kinetics shrinkage parameter -\item $g_i$ is is the kinetics tumor growth parameter +\item $g_i$ is is the kinetics tumour growth parameter \item $\phi_i$ is the proportion of cells affected by the treatment \end{itemize} diff --git a/design/bibliography.bib b/design/bibliography.bib index 8473f5a2..459429b5 100755 --- a/design/bibliography.bib +++ b/design/bibliography.bib @@ -1,5 +1,5 @@ @article{kerioui2020bayesian, - title={Bayesian inference using Hamiltonian Monte-Carlo algorithm for nonlinear joint modeling in the context of cancer immunotherapy}, + title={Bayesian inference using Hamiltonian Monte-Carlo algorithm for nonlinear joint modelling in the context of cancer immunotherapy}, author={Kerioui, Marion and Mercier, Francois and Bertrand, Julie and Tardivon, Coralie and Bruno, Ren{\'e} and Guedj, J{\'e}r{\'e}mie and Desm{\'e}e, Sol{\`e}ne}, journal={Statistics in Medicine}, volume={39}, diff --git a/design/compare_sim_gsf.Rmd b/design/compare_sim_gsf.Rmd index 15c9b8cb..964594e0 100644 --- a/design/compare_sim_gsf.Rmd +++ b/design/compare_sim_gsf.Rmd @@ -18,22 +18,22 @@ knitr::opts_chunk$set(echo = TRUE) #' #' Simulation code slightly adapted from supplementary materials from #' Kerioui, M, Mercier, F, Bertrand, J, et al. Bayesian inference using Hamiltonian -#' Monte-Carlo algorithm for nonlinear joint modeling in the context of cancer immunotherapy. +#' Monte-Carlo algorithm for nonlinear joint modelling in the context of cancer immunotherapy. #' Statistics in Medicine. 2020; 39: 4853– 4868. https://doi.org/10.1002/sim.8756 #' #' @param N number of patients #' @param BSLD baseline sum of longest diameters (SLD) -#' @param s exponential tumor shrinkage rate -#' @param g exponential tumor growth rate +#' @param s exponential tumour shrinkage rate +#' @param g exponential tumour growth rate #' @param phi proportion of cells responding to treatment #' @param omega_BSLD standard deviation of random effects for `BSLD` #' @param omega_s standard deviation of random effects for `s` #' @param omega_g standard deviation of random effects for `g` #' @param omega_phi standard deviation of random effects for `phi` -#' @param sigma standard deviation of of tumor size +#' @param sigma standard deviation of of tumour size #' @param lambda scale parameter for Weibull survival distribution #' @param beta coefficient of link of SLD in proportional hazards -#' @param times observation times for tumor size. Should not exceed 693. +#' @param times observation times for tumour size. Should not exceed 693. #' #' @return A data.frame with columns #' ID: patient id @@ -310,7 +310,6 @@ s2_stan_data <- DataJoint( ```{r} jm <- JointModel( - link = LinkGSF(), longitudinal = LongitudinalGSF( omega_ks = prior_lognormal(log(1), .5, init = .1), omega_kg = prior_lognormal(log(1), .5, init = .1), @@ -318,7 +317,11 @@ jm <- JointModel( mu_kg = prior_lognormal(log(0.1), 0.5, init = 0.005), mu_bsld = prior_lognormal(mu = log(55), sigma = 1.5, init = 55) ), - survival = SurvivalExponential() + survival = SurvivalExponential(), + link = Link( + link_dsld(), + link_ttg() + ) ) diff --git a/design/example_of_use.R b/design/example_of_use.R index 2ce275a7..638c6d33 100755 --- a/design/example_of_use.R +++ b/design/example_of_use.R @@ -19,8 +19,8 @@ devtools::load_all(export_all = FALSE) jm <- JointModel( longitudinal = LongitudinalRandomSlope(), - link = LinkRandomSlope(), - survival = SurvivalWeibullPH() + survival = SurvivalWeibullPH(), + link = link_dsld() ) @@ -67,8 +67,8 @@ jm <- JointModel( jm <- JointModel( longitudinal = LongitudinalRandomSlope(), - link = LinkRandomSlope(), - survival = SurvivalWeibullPH() + survival = SurvivalWeibullPH(), + link = link_dsld() ) @@ -95,7 +95,7 @@ jlist <- simulate_joint_data( phi = 0.1 ), os_fun = sim_os_weibull( - lambda = 0.00333, # 1/300 + lambda = 0.00333, gamma = 0.97 ) ) diff --git a/design/renvs.png b/design/renvs.png new file mode 100644 index 00000000..f6db6419 Binary files /dev/null and b/design/renvs.png differ diff --git a/design/renvs.png.drawio b/design/renvs.png.drawio new file mode 100644 index 00000000..08155eb9 --- /dev/null +++ b/design/renvs.png.drawio @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/design/tests/gsf-no-link.R b/design/tests/gsf-no-link.R index c2d94b59..ed44c2e6 100644 --- a/design/tests/gsf-no-link.R +++ b/design/tests/gsf-no-link.R @@ -114,12 +114,15 @@ jm <- JointModel( a_phi = prior_lognormal(log(6), 1), b_phi = prior_lognormal(log(8), 1), sigma = prior_lognormal(log(0.01), 1), - centered = FALSE + centred = FALSE ), survival = SurvivalExponential( lambda = prior_lognormal(log(1 / (400 / 365)), 1) ), - link = LinkGSF() + link = Link( + link_dsld(), + link_ttg() + ) ) diff --git a/design/tests/gsf-with-link-identity.R b/design/tests/gsf-with-link-identity.R index 549147d3..5b2959a6 100644 --- a/design/tests/gsf-with-link-identity.R +++ b/design/tests/gsf-with-link-identity.R @@ -85,7 +85,7 @@ jm <- JointModel( survival = SurvivalExponential( lambda = prior_lognormal(log(1 / 400), 0.7) ), - link = LinkGSF(link_gsf_identity(tau = prior_normal(0.002, 0.2))) + link = Link(link_identity(prior_normal(0.002, 0.2))) ) diff --git a/design/tests/gsf-with-link.R b/design/tests/gsf-with-link.R index 70c62560..983f718f 100644 --- a/design/tests/gsf-with-link.R +++ b/design/tests/gsf-with-link.R @@ -87,7 +87,10 @@ jm <- JointModel( tilde_phi = prior_normal(0, 5) ), survival = SurvivalExponential(), - link = LinkGSF() + link = Link( + link_dsld(), + link_ttg() + ) ) diff --git a/design/tests/kerioui-replication.R b/design/tests/kerioui-replication.R index 5a19e1fb..007db32e 100644 --- a/design/tests/kerioui-replication.R +++ b/design/tests/kerioui-replication.R @@ -69,9 +69,7 @@ jm <- JointModel( survival = SurvivalExponential( lambda = prior_lognormal(log(0.0006896552), 1) ), - link = LinkGSF(link_gsf_identity( - tau = prior_normal(0.002, 0.2) - )) + link = Link(link_identity(prior_normal(0.002, 0.2))) ) mp_k <- sampleStanModel( diff --git a/design/tests/rs-with-link.R b/design/tests/rs-with-link.R index da97c748..ee1ccc9f 100644 --- a/design/tests/rs-with-link.R +++ b/design/tests/rs-with-link.R @@ -12,7 +12,12 @@ options("jmpost.cache_dir" = file.path("local", "models")) jm <- JointModel( longitudinal = LongitudinalRandomSlope(), survival = SurvivalExponential(), - link = LinkRandomSlope() + link = Link( + link_dsld(), + link_identity( + prior = prior_normal(0, 0.02) + ) + ) ) @@ -21,11 +26,12 @@ jm <- JointModel( write_stan(jm, "local/debug.stan") +set.seed(514) ## Generate Test data with known parameters jlist <- simulate_joint_data( - n = c(400, 400), + n = c(400, 300), times = 1:2000, - lambda_cen = 1 / 9000, + lambda_cen = 1 / 400, beta_cat = c( "A" = 0, "B" = -0.1, @@ -37,8 +43,8 @@ jlist <- simulate_joint_data( sigma = 3, slope_mu = c(1,3), slope_sigma = 0.2, - phi = 0.1, - .debug = TRUE + link_dsld = -0.1, + link_identity = 0.0001 ), os_fun = sim_os_exponential( lambda = 0.00333 # 1/300 @@ -73,14 +79,17 @@ jdat <- DataJoint( ) ) +init_vals <- initialValues(jm, n_chains = 2) + ## Sample from JointModel mp <- sampleStanModel( jm, data = jdat, iter_sampling = 1000, iter_warmup = 1000, - chains = 1, - parallel_chains = 1 + chains = 2, + parallel_chains = 2, + init = init_vals ) @@ -93,10 +102,12 @@ vars <- c( "lm_rs_slope_mu", # 1 , 3 "lm_rs_slope_sigma", # 0.2 "lm_rs_sigma", # 3 - "link_lm_phi" # 0.1 + "link_dsld", # -0.1 + "link_identity" # 0.0001 ) -mp$summary(vars) +mp@results$summary(vars) + diff --git a/inst/WORDLIST b/inst/WORDLIST index 8f0966fc..9cb3c337 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -56,6 +56,7 @@ oint os paramaters parametrisation +parameterisation rajectories redicting si @@ -116,4 +117,18 @@ xk LogNormal AST throughs +LinkComponent behaviour +initialise +customisation +dSLD +prior's +R's +Stat +etc +bl +Br +BS +nonlinear +sim +u diff --git a/inst/stan/base/base.stan b/inst/stan/base/base.stan index f459b753..6d6be06e 100755 --- a/inst/stan/base/base.stan +++ b/inst/stan/base/base.stan @@ -1,18 +1,8 @@ functions { - // - // Source - base/base.stan - // - -{% if link_none %} - // If user has requested link_none then provide a dummy link_contribution function - // that does nothing - matrix link_contribution(matrix time, matrix pars_lm) { - return rep_matrix(0, rows(time), cols(time)); - } -{% endif %} {{ longitudinal.functions }} {{ survival.functions }} +{{ link.functions }} } @@ -21,7 +11,6 @@ data{ // // Source - base/base.stan // - int Nind; // Number of individuals. int n_studies; // Number of studies. int n_arms; // Number of treatment arms. @@ -29,38 +18,25 @@ data{ array[Nind] int pt_arm_index; // Index of treatment arm per pt (PT index sorted) {{ survival.data }} +{{ link.data }} {{ longitudinal.data }} - // - // Dynamically derived priors - // {{ priors.data }} } transformed data { - {{ longitudinal.transformed_data }} +{{ link.transformed_data }} {{ survival.transformed_data }} - - -{% if link_none %} - // - // Source - base/base.stan - // - - // If user has requested link_none then provide a dummy pars_lm object - // that contains nothing - matrix[Nind, 0] pars_lm = rep_matrix(0, Nind, 0); -{% endif %} - } parameters{ {{ longitudinal.parameters }} +{{ link.parameters }} {{ survival.parameters }} } @@ -75,6 +51,7 @@ transformed parameters{ vector[Nind] log_lik = rep_vector(0.0, Nind); {{ longitudinal.transformed_parameters }} +{{ link.transformed_parameters }} {{ survival.transformed_parameters }} } @@ -82,11 +59,9 @@ transformed parameters{ model{ {{ longitudinal.model }} +{{ link.model }} {{ survival.model }} - // - // Dynamically derived priors - // {{ priors.model }} // @@ -98,6 +73,7 @@ model{ generated quantities{ {{ longitudinal.generated_quantities }} +{{ link.generated_quantities }} {{ survival.generated_quantities }} } diff --git a/inst/stan/base/link.stan b/inst/stan/base/link.stan new file mode 100644 index 00000000..967bf417 --- /dev/null +++ b/inst/stan/base/link.stan @@ -0,0 +1,43 @@ + +functions { + // + // Source - base/link.stan + // + matrix link_contribution( + matrix time, + matrix link_function_inputs, + vector link_coefficients + ) { + + int nrows = rows(time); + int ncols = cols(time); + + matrix[nrows, ncols] matrix_link_contrib = rep_matrix(0, nrows, ncols); + + {% for item in items -%} + matrix_link_contrib += link_coefficients[{{loop.index1}}] .* {{ item }}_contrib( + time, + link_function_inputs + ); + {% endfor -%} + return matrix_link_contrib; + } +} + +parameters{ + // + // Source - base/link.stan + // + {% for item in items -%} + real {{ item }}; + {% endfor -%} + +} + +transformed parameters { + vector[{{ length(items) }}] link_coefficients; + {% for item in items -%} + link_coefficients[{{loop.index1}}] = {{ item }}; + {% endfor -%} +} + diff --git a/inst/stan/base/link_none.stan b/inst/stan/base/link_none.stan new file mode 100644 index 00000000..c373301c --- /dev/null +++ b/inst/stan/base/link_none.stan @@ -0,0 +1,28 @@ + + +functions { + // + // Source - base/link_none.stan + // + // If user has requested link_none then provide a dummy link_contribution function + // that does nothing + matrix link_contribution( + matrix time, + matrix link_function_inputs, + vector link_coefficients + ) { + return rep_matrix(0, rows(time), cols(time)); + } +} + +transformed data { + // + // Source - base/link_none.stan + // + // If user has requested link_none then provide a dummy pars_lm object + // that contains nothing + matrix[Nind, 0] link_function_inputs = rep_matrix(0, Nind, 0); + vector[0] link_coefficients; +} + + diff --git a/inst/stan/base/longitudinal.stan b/inst/stan/base/longitudinal.stan index 6e5af1d2..90faad06 100755 --- a/inst/stan/base/longitudinal.stan +++ b/inst/stan/base/longitudinal.stan @@ -6,33 +6,33 @@ data{ // // Longitudinal data - int Nta_total; // Total number of tumor assessments. - int Nta_obs_y; // Number of observed tumor assessments (not censored). - int Nta_cens_y; // Number of censored tumor assessments (below threshold). + int Nta_total; // Total number of tumour assessments. + int Nta_obs_y; // Number of observed tumour assessments (not censored). + int Nta_cens_y; // Number of censored tumour assessments (below threshold). - array[Nta_total] int ind_index; // Index of individuals for each tumor assessment. - array[Nta_obs_y] int obs_y_index; // Index of observed tumor assessments (not censored). - array[Nta_cens_y] int cens_y_index; // Index of censored tumor assessments. + array[Nta_total] int ind_index; // Index of individuals for each tumour assessment. + array[Nta_obs_y] int obs_y_index; // Index of observed tumour assessments (not censored). + array[Nta_cens_y] int cens_y_index; // Index of censored tumour assessments. vector[Nta_total] Yobs; // Array of individual responses. vector[Nta_total] Tobs; // Individual timepoints. real Ythreshold; // Censoring threshold. - // Matrix of individuals x observed tumor assessments (sparse matrix of 0s and 1s), + // Matrix of individuals x observed tumour assessments (sparse matrix of 0s and 1s), // so the dimension is Nind x Nta_obs_y. array [3] int n_mat_inds_obs_y; vector[n_mat_inds_obs_y[1]] w_mat_inds_obs_y; array[n_mat_inds_obs_y[2]] int v_mat_inds_obs_y; array[n_mat_inds_obs_y[3]] int u_mat_inds_obs_y; - // Matrix of individuals x censored tumor assessments (sparse matrix of 0s and 1s). + // Matrix of individuals x censored tumour assessments (sparse matrix of 0s and 1s). // so the dimension is Nind x Nta_cens_y. array [3] int n_mat_inds_cens_y; vector[n_mat_inds_cens_y[1]] w_mat_inds_cens_y; array[n_mat_inds_cens_y[2]] int v_mat_inds_cens_y; array[n_mat_inds_cens_y[3]] int u_mat_inds_cens_y; - // Matrix of all individuals x tumor assessments (sparse matrix of 0s and 1s). + // Matrix of all individuals x tumour assessments (sparse matrix of 0s and 1s). // so the dimension is Nind x Nta_total. array [3] int n_mat_inds_all_y; vector[n_mat_inds_all_y[1]] w_mat_inds_all_y; diff --git a/inst/stan/base/survival.stan b/inst/stan/base/survival.stan index eea7ca3c..35d3c3c2 100755 --- a/inst/stan/base/survival.stan +++ b/inst/stan/base/survival.stan @@ -1,7 +1,9 @@ functions { + {{ stan.functions }} + // // Source - base/survival.stan // @@ -10,7 +12,8 @@ functions { matrix log_hazard( matrix time, vector pars_os, - matrix pars_lm, + matrix link_function_inputs, + vector link_coefficients, vector cov_contribution ) { //print([rows(time), cols(time), rows(cov_contribution), cols(cov_contribution)]); @@ -20,13 +23,16 @@ functions { cols(time) ); - matrix[rows(time), cols(time)] lm_link_contribution = link_contribution(time, pars_lm); + matrix[rows(time), cols(time)] lm_link_contribution = link_contribution( + time, + link_function_inputs, + link_coefficients + ); matrix[rows(time), cols(time)] result = cov_contribution_matrix + lm_link_contribution + - log_baseline - ; + log_baseline; return result; } @@ -36,7 +42,8 @@ functions { vector log_survival( vector time, vector pars_os, - matrix pars_lm, + matrix link_function_inputs, + vector link_coefficients, data vector nodes, data vector weights, vector cov_contribution @@ -49,7 +56,8 @@ functions { log_hazard( node_times, pars_os, - pars_lm, + link_function_inputs, + link_coefficients, cov_contribution ) ) @@ -83,7 +91,8 @@ functions { array[] int pt_select_index, vector sm_time_grid, vector pars_os, - matrix pars_lm, + matrix link_function_inputs, + vector link_coefficients, vector cov_contribution ) { int n_pt_select_index = num_elements(pt_select_index); @@ -91,12 +100,15 @@ functions { matrix[n_pt_select_index, n_sm_time_grid] result; for (i in 1:n_pt_select_index) { int current_pt_index = pt_select_index[i]; - result[i, ] = to_row_vector(log_hazard( - rep_matrix(sm_time_grid, 1), - pars_os, - rep_matrix(pars_lm[current_pt_index, ], n_sm_time_grid), - rep_vector(cov_contribution[current_pt_index], n_sm_time_grid) - )); + result[i, ] = to_row_vector( + log_hazard( + rep_matrix(sm_time_grid, 1), + pars_os, + rep_matrix(link_function_inputs[current_pt_index, ], n_sm_time_grid), + link_coefficients, + rep_vector(cov_contribution[current_pt_index], n_sm_time_grid) + ) + ); } return result; } @@ -106,7 +118,8 @@ functions { array[] int pt_select_index, vector sm_time_grid, vector pars_os, - matrix pars_lm, + matrix link_function_inputs, + vector link_coefficients, data vector nodes, data vector weights, vector cov_contribution @@ -116,14 +129,17 @@ functions { matrix[n_pt_select_index, n_sm_time_grid] result; for (i in 1:n_pt_select_index) { int current_pt_index = pt_select_index[i]; - result[i, ] = to_row_vector(log_survival( - sm_time_grid, - pars_os, - rep_matrix(pars_lm[current_pt_index, ], n_sm_time_grid), - nodes, - weights, - rep_vector(cov_contribution[current_pt_index], n_sm_time_grid) - )); + result[i, ] = to_row_vector( + log_survival( + sm_time_grid, + pars_os, + rep_matrix(link_function_inputs[current_pt_index, ], n_sm_time_grid), + link_coefficients, + nodes, + weights, + rep_vector(cov_contribution[current_pt_index], n_sm_time_grid) + ) + ); } return result; } @@ -200,7 +216,8 @@ transformed parameters { log_surv_fit_at_obs_times[time_positive_index] += log_survival( Times[time_positive_index], pars_os, - pars_lm[time_positive_index], + link_function_inputs[time_positive_index], + link_coefficients, nodes, weights, os_cov_contribution[time_positive_index] @@ -214,7 +231,8 @@ transformed parameters { log_hazard( to_matrix(Times[dead_ind_index]), pars_os, - pars_lm[dead_ind_index], + link_function_inputs[dead_ind_index], + link_coefficients, os_cov_contribution[dead_ind_index] ) ); @@ -237,7 +255,8 @@ generated quantities { pt_select_index, sm_time_grid, pars_os, - pars_lm, + link_function_inputs, + link_coefficients, nodes, weights, os_cov_contribution @@ -246,7 +265,8 @@ generated quantities { pt_select_index, sm_time_grid, pars_os, - pars_lm, + link_function_inputs, + link_coefficients, os_cov_contribution ); } diff --git a/inst/stan/lm-gsf/link.stan b/inst/stan/lm-gsf/link.stan index 45a080a6..d70de732 100755 --- a/inst/stan/lm-gsf/link.stan +++ b/inst/stan/lm-gsf/link.stan @@ -1,55 +1,16 @@ -functions { - // - // Source - lm-gsf/link.stan - // - matrix link_contribution(matrix time, matrix pars_lm) { - - int nrows = rows(time); - int ncols = cols(time); - - matrix[nrows, ncols] matrix_link_contrib = rep_matrix(0, nrows, ncols); - vector[nrows] psi_bsld = col(pars_lm, 1); - vector[nrows] psi_ks = col(pars_lm, 2); - vector[nrows] psi_kg = col(pars_lm, 3); - vector[nrows] psi_phi = col(pars_lm, 4); - - {% for item in items -%} - real {{ item.parameter }} = col(pars_lm, {{loop.index1 + 4}})[1]; - matrix_link_contrib += {{ item.parameter }} .* {{ item.contribution_function }}( - time, - psi_bsld, - psi_ks, - psi_kg, - psi_phi - ); - {% endfor -%} - return matrix_link_contrib; - } -} - -parameters{ - // - // Source - lm-gsf/link.stan - // - {% for item in items -%} - real {{ item.parameter }}; - {% endfor -%} -} transformed parameters { // // Source - lm-gsf/link.stan // - matrix[Nind, {{ 4 + length(items)}}] pars_lm = rep_matrix( 0, Nind, {{ 4 + length(items)}}); - - pars_lm[,1] = lm_gsf_psi_bsld; - pars_lm[,2] = lm_gsf_psi_ks; - pars_lm[,3] = lm_gsf_psi_kg; - pars_lm[,4] = lm_gsf_psi_phi; - {% for item in items -%} - pars_lm[,{{4 + loop.index1}}] = rep_vector(1, Nind) .* {{ item.parameter }}; - {% endfor -%} + matrix[Nind, 4] link_function_inputs; + link_function_inputs[,1] = lm_gsf_psi_bsld; + link_function_inputs[,2] = lm_gsf_psi_ks; + link_function_inputs[,3] = lm_gsf_psi_kg; + link_function_inputs[,4] = lm_gsf_psi_phi; } + + diff --git a/inst/stan/lm-gsf/link_dsld.stan b/inst/stan/lm-gsf/link_dsld.stan index 087c055e..88311996 100644 --- a/inst/stan/lm-gsf/link_dsld.stan +++ b/inst/stan/lm-gsf/link_dsld.stan @@ -5,15 +5,16 @@ functions { // // Derivative of SLD - matrix link_dsld_contribution( + matrix link_dsld_contrib( matrix time, - vector psi_bsld, - vector psi_ks, - vector psi_kg, - vector psi_phi + matrix link_function_inputs ) { - int nrows = rows(psi_bsld); + int nrows = rows(link_function_inputs); int ncols = cols(time); + vector[nrows] psi_bsld = link_function_inputs[,1]; + vector[nrows] psi_ks = link_function_inputs[,2]; + vector[nrows] psi_kg = link_function_inputs[,3]; + vector[nrows] psi_phi = link_function_inputs[,4]; // Here we assume that psi's are replicated along the rows of the time matrix. matrix[nrows, ncols] psi_bsld_matrix = rep_matrix(psi_bsld, ncols); diff --git a/inst/stan/lm-gsf/link_identity.stan b/inst/stan/lm-gsf/link_identity.stan index e12607e0..9a4216a1 100644 --- a/inst/stan/lm-gsf/link_identity.stan +++ b/inst/stan/lm-gsf/link_identity.stan @@ -5,15 +5,16 @@ functions { // // Identity of SLD - matrix link_identity_contribution( + matrix link_identity_contrib( matrix time, - vector psi_bsld, - vector psi_ks, - vector psi_kg, - vector psi_phi + matrix link_function_inputs ) { - int nrows = rows(psi_bsld); + int nrows = rows(link_function_inputs); int ncols = cols(time); + vector[nrows] psi_bsld = link_function_inputs[,1]; + vector[nrows] psi_ks = link_function_inputs[,2]; + vector[nrows] psi_kg = link_function_inputs[,3]; + vector[nrows] psi_phi = link_function_inputs[,4]; matrix[nrows, ncols] result; for (i in 1:ncols) { result[,i] = fmin( @@ -24,5 +25,3 @@ functions { return result; } } - - diff --git a/inst/stan/lm-gsf/link_ttg.stan b/inst/stan/lm-gsf/link_ttg.stan index d62e3b9a..46ee7ec5 100644 --- a/inst/stan/lm-gsf/link_ttg.stan +++ b/inst/stan/lm-gsf/link_ttg.stan @@ -4,15 +4,16 @@ functions { // // Source - lm-gsf/link_ttg.stan // - matrix link_ttg_contribution( + matrix link_ttg_contrib( matrix time, - vector psi_bsld, - vector psi_ks, - vector psi_kg, - vector psi_phi + matrix link_function_inputs ) { - int nrows = rows(psi_bsld); + int nrows = rows(link_function_inputs); int ncols = cols(time); + vector[nrows] psi_bsld = link_function_inputs[,1]; + vector[nrows] psi_ks = link_function_inputs[,2]; + vector[nrows] psi_kg = link_function_inputs[,3]; + vector[nrows] psi_phi = link_function_inputs[,4]; vector[nrows] num = logit(psi_phi) + log(psi_ks ./ psi_kg); vector[nrows] denom = psi_ks + psi_kg; vector[nrows] ttg_contribution = num ./ denom; diff --git a/inst/stan/lm-gsf/model.stan b/inst/stan/lm-gsf/model.stan index cdb35f5b..a49da912 100755 --- a/inst/stan/lm-gsf/model.stan +++ b/inst/stan/lm-gsf/model.stan @@ -15,7 +15,7 @@ parameters{ real lm_gsf_omega_ks; real lm_gsf_omega_kg; -{% if centered -%} +{% if centred -%} vector[Nind] lm_gsf_psi_bsld; vector[Nind] lm_gsf_psi_ks; vector[Nind] lm_gsf_psi_kg; @@ -44,7 +44,7 @@ transformed parameters{ // Source - lm-gsf/model.stan // -{% if not centered -%} +{% if not centred -%} vector[Nind] lm_gsf_psi_bsld = exp( lm_gsf_mu_bsld[pt_study_index] + (lm_gsf_eta_tilde_bsld * lm_gsf_omega_bsld) ); @@ -100,7 +100,7 @@ model { // // Source - lm-gsf/model.stan // -{% if centered %} +{% if centred %} lm_gsf_psi_bsld ~ lognormal(lm_gsf_mu_bsld[pt_study_index], lm_gsf_omega_bsld); lm_gsf_psi_ks ~ lognormal(lm_gsf_mu_ks[pt_arm_index], lm_gsf_omega_ks); lm_gsf_psi_kg ~ lognormal(lm_gsf_mu_kg[pt_arm_index], lm_gsf_omega_kg); diff --git a/inst/stan/lm-random-slope/link.stan b/inst/stan/lm-random-slope/link.stan new file mode 100644 index 00000000..dc953b48 --- /dev/null +++ b/inst/stan/lm-random-slope/link.stan @@ -0,0 +1,10 @@ + +transformed parameters { + // + // Source - lm-random-slope/link.stan + // + + matrix[Nind, 2] link_function_inputs; + link_function_inputs[, 1] = rep_vector(lm_rs_intercept, Nind); + link_function_inputs[, 2] = lm_rs_ind_rnd_slope; +} diff --git a/inst/stan/lm-random-slope/link_dsld.stan b/inst/stan/lm-random-slope/link_dsld.stan new file mode 100644 index 00000000..0ea8bd41 --- /dev/null +++ b/inst/stan/lm-random-slope/link_dsld.stan @@ -0,0 +1,20 @@ + +functions { + // + // Source - lm-random-slope/link_dsld.stan + // + + // Derivative of SLD + matrix link_dsld_contrib( + matrix time, + matrix link_function_inputs + ) { + int nrows = rows(time); + int ncols = cols(time); + vector[nrows] lm_rs_ind_rnd_slope = link_function_inputs[,2]; + matrix[nrows, ncols] rnd_slope_mat = rep_matrix(lm_rs_ind_rnd_slope, ncols); + return rnd_slope_mat; + } +} + + diff --git a/inst/stan/lm-random-slope/link_identity.stan b/inst/stan/lm-random-slope/link_identity.stan new file mode 100644 index 00000000..d0d9b1f7 --- /dev/null +++ b/inst/stan/lm-random-slope/link_identity.stan @@ -0,0 +1,20 @@ + +functions { + // + // Source - lm-gsf/link_identity.stan + // + + // Identity of SLD + matrix link_identity_contrib( + matrix time, + matrix link_function_inputs + ) { + int ncols = cols(time); + int nrows = rows(time); + matrix[nrows, ncols] intercept = rep_matrix(link_function_inputs[,1], ncols); + matrix[nrows, ncols] slope = rep_matrix(link_function_inputs[,2], ncols); + return intercept + (slope .* time); + } +} + + diff --git a/inst/stan/lm-random-slope/links.stan b/inst/stan/lm-random-slope/links.stan deleted file mode 100755 index bd8f2829..00000000 --- a/inst/stan/lm-random-slope/links.stan +++ /dev/null @@ -1,17 +0,0 @@ - -functions { - // LinkRandomSlope - matrix link_contribution(matrix time, matrix pars_lm) { - return rep_matrix(to_vector(pars_lm), cols(time)); - } -} - -transformed parameters { - // LinkRandomSlope - matrix[Nind, 1] pars_lm = to_matrix(lm_rs_ind_rnd_slope .* link_lm_phi); -} - -parameters { - // LinkRandomSlope - real link_lm_phi; -} diff --git a/man/JointModel-class.Rd b/man/JointModel-class.Rd index a5d8ce8a..c7a3185b 100644 --- a/man/JointModel-class.Rd +++ b/man/JointModel-class.Rd @@ -7,7 +7,7 @@ \alias{JointModel} \title{Joint Model Object and Constructor Function} \usage{ -JointModel(longitudinal = NULL, survival = NULL, link = NULL) +JointModel(longitudinal = NULL, survival = NULL, link = link_none()) } \arguments{ \item{longitudinal}{(\code{\link{LongitudinalModel}} or \code{NULL})\cr the longitudinal model.} @@ -35,6 +35,7 @@ Joint Model Object and Constructor Function \seealso{ Other JointModel: +\code{\link{as.StanModule.JointModel}()}, \code{\link{as.character.JointModel}()} } \concept{JointModel} diff --git a/man/Link-Shared.Rd b/man/Link-Shared.Rd new file mode 100644 index 00000000..8a52a23e --- /dev/null +++ b/man/Link-Shared.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Link.R +\name{Link-Shared} +\alias{Link-Shared} +\title{\code{Link} Function Arguments} +\arguments{ +\item{x}{(\code{\link{Link}})\cr a link object.} + +\item{object}{(\code{\link{Link}})\cr a link object.} + +\item{...}{Not Used.} +} +\description{ +This exists just to contain all the common arguments for \code{\link{Link}} methods. +} +\keyword{internal} diff --git a/man/Link-class.Rd b/man/Link-class.Rd index afd0f599..7a101b09 100644 --- a/man/Link-class.Rd +++ b/man/Link-class.Rd @@ -7,32 +7,32 @@ \alias{Link} \title{\code{Link}} \usage{ -Link( - stan = StanModule(), - parameters = ParameterList(), - name = "", - ... -) +Link(...) } \arguments{ -\item{stan}{(\code{StanModule})\cr code containing the Stan code specification.} - -\item{parameters}{(\code{ParameterList})\cr the parameter specification.} - -\item{name}{(\code{character})\cr display name for the model object.} - -\item{...}{additional arguments passed to the constructor.} +\item{...}{(\code{\link{LinkComponent}})\cr an arbitrary number of link components.} } \description{ -\code{Link} +Simple container class to enable the use of multiple link components in a joint model. +Note that the constructor of this object is idempotent e.g. \code{Link(Link(x)) == Link(x)} } \section{Slots}{ \describe{ -\item{\code{stan}}{(\code{StanModule})\cr code containing the link specification.} - -\item{\code{parameters}}{(\code{ParameterList})\cr the parameter specification.} - -\item{\code{name}}{(\code{character})\cr display name for the link object.} +\item{\code{components}}{(\code{list})\cr a list of \code{\link{LinkComponent}} objects.} }} +\examples{ +Link( + link_dsld(), + link_ttg() +) + +} +\seealso{ +Other Link: +\code{\link{as.StanModule.Link}()}, +\code{\link{as.list.Link}()}, +\code{\link{length.Link}()} +} +\concept{Link} diff --git a/man/LinkComponent-Shared.Rd b/man/LinkComponent-Shared.Rd new file mode 100644 index 00000000..e60ac48d --- /dev/null +++ b/man/LinkComponent-Shared.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/LinkComponent.R +\name{LinkComponent-Shared} +\alias{LinkComponent-Shared} +\title{\code{LinkComponent} Function Arguments} +\arguments{ +\item{stan}{(\code{StanModule})\cr Stan code.} + +\item{x}{(\code{\link{LinkComponent}})\cr a link component.} + +\item{object}{(\code{\link{LinkComponent}})\cr a link component.} + +\item{...}{Not Used.} +} +\description{ +This exists just to contain all the common arguments for \code{\link{LinkComponent}} methods. +} +\keyword{internal} diff --git a/man/LinkComponent-class.Rd b/man/LinkComponent-class.Rd new file mode 100644 index 00000000..f66ee1e5 --- /dev/null +++ b/man/LinkComponent-class.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/LinkComponent.R +\docType{class} +\name{LinkComponent-class} +\alias{LinkComponent-class} +\alias{.LinkComponent} +\alias{LinkComponent} +\title{\code{LinkComponent}} +\usage{ +LinkComponent(stan, parameters = ParameterList(), key = "", ...) +} +\arguments{ +\item{stan}{(\code{StanModule} or \code{function})\cr Stan code. See Details.} + +\item{parameters}{(\code{ParameterList})\cr The parameter specification.} + +\item{key}{(\code{character})\cr Link identifier. See Details.} + +\item{...}{additional arguments for \code{\link[=StanModel]{StanModel()}}.} +} +\description{ +\code{LinkComponent} +} +\details{ +This object provides key information needed to construct a link contribution in the +survival model based on the parameters of the longitudinal model. + +Each link component defines a stan function of the longitudinal model parameters which is +multiplied by a model coefficient and added to the survival models hazard function. + +For full details about the specification of a \code{LinkComponent} please see +\code{vignette("extending-jmpost", package = "jmpost")}. + +The \code{stan} argument can be either a \code{StanModule} object or a function. +If a function is provided, it must take a single argument, a +\code{LongitudinalModel} object, and return a \code{StanModule} object. This allows for +generic functions to be used for links such as \code{dsld} which allows for each model +to provide their own model specific implementation. +} +\section{Slots}{ + +\describe{ +\item{\code{stan}}{(\code{StanModule})\cr See Arguments.} + +\item{\code{parameters}}{(\code{ParameterList})\cr See Arguments.} + +\item{\code{name}}{(\code{character})\cr See Arguments.} +}} + +\seealso{ +Other LinkComponent: +\code{\link{as.StanModule.LinkComponent}()}, +\code{\link{as.list.LinkComponent}()}, +\code{\link{getParameters}()}, +\code{\link{initialValues}()} +} +\concept{LinkComponent} diff --git a/man/LinkGSF-class.Rd b/man/LinkGSF-class.Rd deleted file mode 100644 index 6c16dcfa..00000000 --- a/man/LinkGSF-class.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/LinkGSF.R -\docType{class} -\name{LinkGSF-class} -\alias{LinkGSF-class} -\alias{.LinkGSF} -\alias{LinkGSF} -\title{\code{LinkGSF}} -\usage{ -LinkGSF(...) -} -\arguments{ -\item{...}{(\code{link_gsf_abstract})\cr which link components should be included. If no arguments -are provided then this will be set to \code{\link[=link_gsf_dsld]{link_gsf_dsld()}} and \code{\link[=link_gsf_ttg]{link_gsf_ttg()}}} -} -\description{ -This class extends the general \code{\link[=Link-class]{Link}} class for -the \code{\link[=LongitudinalGSF-class]{LongitudinalGSF}} -model. -} diff --git a/man/LinkNone-class.Rd b/man/LinkNone-class.Rd deleted file mode 100644 index b3766d64..00000000 --- a/man/LinkNone-class.Rd +++ /dev/null @@ -1,15 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/LinkNone.R -\docType{class} -\name{LinkNone-class} -\alias{LinkNone-class} -\alias{.LinkNone} -\alias{LinkNone} -\title{\code{LinkNone}} -\usage{ -LinkNone() -} -\description{ -This class extends the general \code{\link{Link}} class to specify no link -between the longitudinal and the time-to-event models. -} diff --git a/man/LinkRandomSlope-class.Rd b/man/LinkRandomSlope-class.Rd deleted file mode 100644 index 56d39f74..00000000 --- a/man/LinkRandomSlope-class.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/LinkRandomSlope.R -\docType{class} -\name{LinkRandomSlope-class} -\alias{LinkRandomSlope-class} -\alias{.LinkRandomSlope} -\alias{LinkRandomSlope} -\title{\code{LinkRandomSlope}} -\usage{ -LinkRandomSlope(link_lm_phi = prior_normal(0.2, 0.5)) -} -\arguments{ -\item{link_lm_phi}{(\code{Prior})\cr prior for the link coefficient for the -random slope link contribution.} -} -\description{ -This class extends the general \code{\link{Link}} class for the \code{\link{LongitudinalRandomSlope}} -model. -} diff --git a/man/LongitudinalGSF-class.Rd b/man/LongitudinalGSF-class.Rd index dd026fd2..ce45c572 100644 --- a/man/LongitudinalGSF-class.Rd +++ b/man/LongitudinalGSF-class.Rd @@ -17,7 +17,7 @@ LongitudinalGSF( a_phi = prior_lognormal(log(5), 1), b_phi = prior_lognormal(log(5), 1), sigma = prior_lognormal(log(0.1), 1), - centered = FALSE + centred = FALSE ) } \arguments{ @@ -39,7 +39,7 @@ LongitudinalGSF( \item{sigma}{(\code{Prior})\cr for the variance of the longitudinal values \code{sigma}.} -\item{centered}{(\code{logical})\cr whether to use the centered parameterization.} +\item{centred}{(\code{logical})\cr whether to use the centred parameterization.} } \description{ This class extends the general \code{\link{LongitudinalModel}} class for using the diff --git a/man/addLink.Rd b/man/addLink.Rd deleted file mode 100644 index 3042e756..00000000 --- a/man/addLink.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/generics.R, R/Link.R, R/LinkNone.R, -% R/defaults.R -\name{addLink} -\alias{addLink} -\alias{addLink,LongitudinalModel,Link-method} -\alias{addLink,LongitudinalModel,LinkNone-method} -\alias{addLink,LongitudinalModel,NULL-method} -\alias{addLink,NULL,Link-method} -\alias{addLink,NULL,NULL-method} -\title{\code{addLink}} -\usage{ -addLink(x, y, ...) - -\S4method{addLink}{LongitudinalModel,Link}(x, y, ...) - -\S4method{addLink}{LongitudinalModel,LinkNone}(x, y, ...) - -\S4method{addLink}{LongitudinalModel,NULL}(x, y, ...) - -\S4method{addLink}{NULL,Link}(x, y, ...) - -\S4method{addLink}{NULL,NULL}(x, y, ...) -} -\arguments{ -\item{x}{the longitudinal model.} - -\item{y}{the link to be added.} - -\item{...}{additional arguments.} -} -\description{ -Add a link to a longitudinal model. -} diff --git a/man/as.StanModule.JointModel.Rd b/man/as.StanModule.JointModel.Rd new file mode 100644 index 00000000..a82198d1 --- /dev/null +++ b/man/as.StanModule.JointModel.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/JointModel.R +\name{as.StanModule.JointModel} +\alias{as.StanModule.JointModel} +\title{\code{JointModel} -> \code{StanModule}} +\usage{ +\method{as.StanModule}{JointModel}(object, ...) +} +\arguments{ +\item{object}{(\code{\link{JointModel}}) \cr Joint model specification.} + +\item{...}{Not Used.} +} +\description{ +Converts a \code{\link{JointModel}} object to a \code{\link{StanModule}} object +} +\seealso{ +Other JointModel: +\code{\link{JointModel-class}}, +\code{\link{as.character.JointModel}()} + +Other as.StanModule: +\code{\link{as.StanModule}()}, +\code{\link{as.StanModule.Link}()}, +\code{\link{as.StanModule.LinkComponent}()}, +\code{\link{as.StanModule.Parameter}()}, +\code{\link{as.StanModule.ParameterList}()}, +\code{\link{as.StanModule.Prior}()} +} +\concept{JointModel} +\concept{as.StanModule} diff --git a/man/as.StanModule.Link.Rd b/man/as.StanModule.Link.Rd new file mode 100644 index 00000000..58850085 --- /dev/null +++ b/man/as.StanModule.Link.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Link.R +\name{as.StanModule.Link} +\alias{as.StanModule.Link} +\title{\code{Link} -> \code{StanModule}} +\usage{ +\method{as.StanModule}{Link}(object, ...) +} +\arguments{ +\item{object}{(\code{\link{Link}})\cr a link object.} + +\item{...}{Not Used.} +} +\description{ +Converts a \code{\link{Link}} object to a \code{\link{StanModule}} object +} +\seealso{ +Other Link: +\code{\link{Link-class}}, +\code{\link{as.list.Link}()}, +\code{\link{length.Link}()} + +Other as.StanModule: +\code{\link{as.StanModule}()}, +\code{\link{as.StanModule.JointModel}()}, +\code{\link{as.StanModule.LinkComponent}()}, +\code{\link{as.StanModule.Parameter}()}, +\code{\link{as.StanModule.ParameterList}()}, +\code{\link{as.StanModule.Prior}()} +} +\concept{Link} +\concept{as.StanModule} diff --git a/man/as.StanModule.LinkComponent.Rd b/man/as.StanModule.LinkComponent.Rd new file mode 100644 index 00000000..9e0cda8a --- /dev/null +++ b/man/as.StanModule.LinkComponent.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/LinkComponent.R +\name{as.StanModule.LinkComponent} +\alias{as.StanModule.LinkComponent} +\title{\code{LinkComponent} -> \code{StanModule}} +\usage{ +\method{as.StanModule}{LinkComponent}(object, model = NULL, ...) +} +\arguments{ +\item{object}{(\code{\link{LinkComponent}})\cr a link component.} + +\item{model}{(\code{LongitudinalModel})\cr The longitudinal model.} + +\item{...}{Not Used.} +} +\description{ +Converts a \code{\link{LinkComponent}} object to a \code{\link{StanModule}} object +} +\seealso{ +Other LinkComponent: +\code{\link{LinkComponent-class}}, +\code{\link{as.list.LinkComponent}()}, +\code{\link{getParameters}()}, +\code{\link{initialValues}()} + +Other as.StanModule: +\code{\link{as.StanModule}()}, +\code{\link{as.StanModule.JointModel}()}, +\code{\link{as.StanModule.Link}()}, +\code{\link{as.StanModule.Parameter}()}, +\code{\link{as.StanModule.ParameterList}()}, +\code{\link{as.StanModule.Prior}()} +} +\concept{LinkComponent} +\concept{as.StanModule} diff --git a/man/as.StanModule.Parameter.Rd b/man/as.StanModule.Parameter.Rd index f57ec1d1..53cbd1f5 100644 --- a/man/as.StanModule.Parameter.Rd +++ b/man/as.StanModule.Parameter.Rd @@ -23,6 +23,9 @@ Other Parameter: Other as.StanModule: \code{\link{as.StanModule}()}, +\code{\link{as.StanModule.JointModel}()}, +\code{\link{as.StanModule.Link}()}, +\code{\link{as.StanModule.LinkComponent}()}, \code{\link{as.StanModule.ParameterList}()}, \code{\link{as.StanModule.Prior}()} } diff --git a/man/as.StanModule.ParameterList.Rd b/man/as.StanModule.ParameterList.Rd index 5402dc1c..9bf0ebb4 100644 --- a/man/as.StanModule.ParameterList.Rd +++ b/man/as.StanModule.ParameterList.Rd @@ -24,6 +24,9 @@ Other ParameterList: Other as.StanModule: \code{\link{as.StanModule}()}, +\code{\link{as.StanModule.JointModel}()}, +\code{\link{as.StanModule.Link}()}, +\code{\link{as.StanModule.LinkComponent}()}, \code{\link{as.StanModule.Parameter}()}, \code{\link{as.StanModule.Prior}()} } diff --git a/man/as.StanModule.Prior.Rd b/man/as.StanModule.Prior.Rd index 3629b845..37818e77 100644 --- a/man/as.StanModule.Prior.Rd +++ b/man/as.StanModule.Prior.Rd @@ -25,6 +25,9 @@ Other Prior-internal: Other as.StanModule: \code{\link{as.StanModule}()}, +\code{\link{as.StanModule.JointModel}()}, +\code{\link{as.StanModule.Link}()}, +\code{\link{as.StanModule.LinkComponent}()}, \code{\link{as.StanModule.Parameter}()}, \code{\link{as.StanModule.ParameterList}()} } diff --git a/man/as.StanModule.Rd b/man/as.StanModule.Rd index 5e2d6995..71ce161c 100644 --- a/man/as.StanModule.Rd +++ b/man/as.StanModule.Rd @@ -1,13 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/generics.R, R/Link.R +% Please edit documentation in R/generics.R \name{as.StanModule} \alias{as.StanModule} -\alias{as.StanModule.Link} \title{\code{as.StanModule}} \usage{ as.StanModule(object, ...) - -\method{as.StanModule}{Link}(object) } \arguments{ \item{object}{what to convert.} @@ -19,6 +16,9 @@ Converts an object into a \code{\link{StanModule}}. } \seealso{ Other as.StanModule: +\code{\link{as.StanModule.JointModel}()}, +\code{\link{as.StanModule.Link}()}, +\code{\link{as.StanModule.LinkComponent}()}, \code{\link{as.StanModule.Parameter}()}, \code{\link{as.StanModule.ParameterList}()}, \code{\link{as.StanModule.Prior}()} diff --git a/man/as.character.JointModel.Rd b/man/as.character.JointModel.Rd index 4af50f70..cf4d0644 100644 --- a/man/as.character.JointModel.Rd +++ b/man/as.character.JointModel.Rd @@ -16,6 +16,7 @@ Renders a \code{\link{JointModel}} object to a stan program } \seealso{ Other JointModel: -\code{\link{JointModel-class}} +\code{\link{JointModel-class}}, +\code{\link{as.StanModule.JointModel}()} } \concept{JointModel} diff --git a/man/as.list.Link.Rd b/man/as.list.Link.Rd new file mode 100644 index 00000000..9d6fc931 --- /dev/null +++ b/man/as.list.Link.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Link.R +\name{as.list.Link} +\alias{as.list.Link} +\title{\code{Link} -> \code{list}} +\usage{ +\method{as.list}{Link}(x, ...) +} +\arguments{ +\item{x}{(\code{\link{Link}})\cr a link object.} + +\item{...}{Not Used.} +} +\description{ +Returns a named list where each element of the list corresponds +to a Stan modelling block e.g. \code{data}, \code{model}, etc. +} +\seealso{ +Other Link: +\code{\link{Link-class}}, +\code{\link{as.StanModule.Link}()}, +\code{\link{length.Link}()} +} +\concept{Link} diff --git a/man/as.list.LinkComponent.Rd b/man/as.list.LinkComponent.Rd new file mode 100644 index 00000000..96486f63 --- /dev/null +++ b/man/as.list.LinkComponent.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/LinkComponent.R +\name{as.list.LinkComponent} +\alias{as.list.LinkComponent} +\title{\code{LinkComponent} -> \code{list}} +\usage{ +\method{as.list}{LinkComponent}(x, ...) +} +\arguments{ +\item{x}{(\code{\link{LinkComponent}})\cr a link component.} + +\item{...}{Not Used.} +} +\description{ +Returns a named list where each element of the list corresponds +to a Stan modelling block e.g. \code{data}, \code{model}, etc. +} +\seealso{ +Other LinkComponent: +\code{\link{LinkComponent-class}}, +\code{\link{as.StanModule.LinkComponent}()}, +\code{\link{getParameters}()}, +\code{\link{initialValues}()} +} +\concept{LinkComponent} diff --git a/man/getParameters.Rd b/man/getParameters.Rd index 052c1f9e..b96cd9f3 100644 --- a/man/getParameters.Rd +++ b/man/getParameters.Rd @@ -1,18 +1,37 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/generics.R, R/StanModel.R +% Please edit documentation in R/generics.R, R/StanModel.R, R/LinkComponent.R, +% R/Link.R, R/defaults.R \name{getParameters} \alias{getParameters} \alias{getParameters.StanModel} +\alias{getParameters.LinkComponent} +\alias{getParameters.Link} +\alias{getParameters.default} \title{\code{getParameters}} \usage{ getParameters(object) \method{getParameters}{StanModel}(object) + +\method{getParameters}{LinkComponent}(object, ...) + +\method{getParameters}{Link}(object, ...) + +\method{getParameters}{default}(object) } \arguments{ \item{object}{where to obtain the parameters from.} } \description{ -Obtain the parameters from a \code{\link{StanModel}}. +Extract any modelling parameters as a \code{\link{ParameterList}} object +from a model. +} +\seealso{ +Other LinkComponent: +\code{\link{LinkComponent-class}}, +\code{\link{as.StanModule.LinkComponent}()}, +\code{\link{as.list.LinkComponent}()}, +\code{\link{initialValues}()} } +\concept{LinkComponent} \keyword{internal} diff --git a/man/initialValues.Rd b/man/initialValues.Rd index 25536f89..a8d651c3 100644 --- a/man/initialValues.Rd +++ b/man/initialValues.Rd @@ -1,9 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/generics.R, R/StanModel.R, R/Link.R, -% R/JointModel.R +% Please edit documentation in R/generics.R, R/StanModel.R, R/LinkComponent.R, +% R/Link.R, R/JointModel.R \name{initialValues} \alias{initialValues} \alias{initialValues.StanModel} +\alias{initialValues.LinkComponent} \alias{initialValues.Link} \alias{initialValues.JointModel} \title{\code{initialValues}} @@ -12,7 +13,9 @@ initialValues(object, ...) \method{initialValues}{StanModel}(object, n_chains, ...) -\method{initialValues}{Link}(object, n_chains, ...) +\method{initialValues}{LinkComponent}(object, n_chains, ...) + +\method{initialValues}{Link}(object, ...) \method{initialValues}{JointModel}(object, n_chains, ...) } @@ -35,3 +38,11 @@ to generate. See the Vignette for further details of how to specify initial values. } +\seealso{ +Other LinkComponent: +\code{\link{LinkComponent-class}}, +\code{\link{as.StanModule.LinkComponent}()}, +\code{\link{as.list.LinkComponent}()}, +\code{\link{getParameters}()} +} +\concept{LinkComponent} diff --git a/man/jmpost-package.Rd b/man/jmpost-package.Rd index 1abe3b75..7fd987f5 100644 --- a/man/jmpost-package.Rd +++ b/man/jmpost-package.Rd @@ -8,7 +8,7 @@ \description{ \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} -Implements joint models combining a non-linear mixed effects model for longitudinal measurements with a time-to-event model for the survival outcome. The acronym 'jmpost' stands for Joint Models for Predicting Overall Survival Trajectories, which refers to the application. In phase 1 studies patients are followed until progression only. Thus, reliable overall survival data and hence estimates are not available. However, we can use additional information from previous clinical trials or real-world data - we can correlate the tumor response data, that are longitudinal measurements, with the overall survival of the patients or their hazard ratios. Thereby we can predict the overall survival from our phase 1 study data and therefore make better decisions. +Implements joint models combining a non-linear mixed effects model for longitudinal measurements with a time-to-event model for the survival outcome. The acronym 'jmpost' stands for Joint Models for Predicting Overall Survival Trajectories, which refers to the application. In phase 1 studies patients are followed until progression only. Thus, reliable overall survival data and hence estimates are not available. However, we can use additional information from previous clinical trials or real-world data - we can correlate the tumour response data, that are longitudinal measurements, with the overall survival of the patients or their hazard ratios. Thereby we can predict the overall survival from our phase 1 study data and therefore make better decisions. } \author{ \strong{Maintainer}: Craig Gower-Page \email{craig.gower-page@roche.com} diff --git a/man/length.Link.Rd b/man/length.Link.Rd new file mode 100644 index 00000000..31316c08 --- /dev/null +++ b/man/length.Link.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Link.R +\name{length.Link} +\alias{length.Link} +\title{\code{Link} -> \code{list}} +\usage{ +\method{length}{Link}(x) +} +\arguments{ +\item{x}{(\code{\link{Link}})\cr a link object.} +} +\description{ +Returns the number of link components within the \code{\link{Link}} object +} +\seealso{ +Other Link: +\code{\link{Link-class}}, +\code{\link{as.StanModule.Link}()}, +\code{\link{as.list.Link}()} +} +\concept{Link} diff --git a/man/link_gsf_abstract-class.Rd b/man/link_gsf_abstract-class.Rd deleted file mode 100644 index 2bc2c63e..00000000 --- a/man/link_gsf_abstract-class.Rd +++ /dev/null @@ -1,43 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/LinkGSF.R -\docType{class} -\name{link_gsf_abstract-class} -\alias{link_gsf_abstract-class} -\alias{.link_gsf_abstract} -\alias{link_gsf_abstract} -\title{\code{link_gsf_abstract}} -\usage{ -link_gsf_abstract(stan, parameter, parameter_name, contribution_fname, name) -} -\arguments{ -\item{stan}{(\code{StanModule})\cr code containing the Stan code specification.} - -\item{parameter}{(\code{ParameterList})\cr the (single) parameter specification.} - -\item{parameter_name}{(\code{character})\cr the name of the parameter.} - -\item{contribution_fname}{(\code{character})\cr the function name of the contribution.} - -\item{name}{(\code{character})\cr display name for the model object.} -} -\description{ -Class to specify the contents for \code{\link{LinkGSF}}. -} -\section{Slots}{ - -\describe{ -\item{\code{stan}}{(\code{StanModule})\cr the Stan code.} - -\item{\code{name}}{(\code{character})\cr the link name to be displayed.} - -\item{\code{parameter}}{(\code{ParameterList})\cr the parameter specification.} - -\item{\code{parameter_name}}{(\code{character})\cr the name of the parameter.} - -\item{\code{contribution_fname}}{(\code{character})\cr the function name of the contribution.} -}} - -\note{ -Only the \code{functions} part of \code{stan} will be used. -} -\keyword{internal} diff --git a/man/link_gsf_dsld-class.Rd b/man/link_gsf_dsld-class.Rd deleted file mode 100644 index a58df51d..00000000 --- a/man/link_gsf_dsld-class.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/LinkGSF.R -\docType{class} -\name{link_gsf_dsld-class} -\alias{link_gsf_dsld-class} -\alias{.link_gsf_dsld} -\alias{link_gsf_dsld} -\title{\code{link_gsf_dsld}} -\usage{ -link_gsf_dsld(beta = prior_normal(0, 5)) -} -\arguments{ -\item{beta}{(\code{Prior})\cr prior for the link coefficient \code{beta}.} -} -\description{ -This class extends the general \code{\link{link_gsf_abstract}} for the derivative of the -sum of longest diameters (\code{dsld}) link contribution. -} diff --git a/man/link_gsf_identity-class.Rd b/man/link_gsf_identity-class.Rd deleted file mode 100644 index 9f586e1d..00000000 --- a/man/link_gsf_identity-class.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/LinkGSF.R -\docType{class} -\name{link_gsf_identity-class} -\alias{link_gsf_identity-class} -\alias{.link_gsf_identity} -\alias{link_gsf_identity} -\title{\code{link_gsf_identity}} -\usage{ -link_gsf_identity(tau = prior_normal(0, 5)) -} -\arguments{ -\item{tau}{(\code{Prior})\cr prior for the link coefficient \code{tau}.} -} -\description{ -This class extends the general \code{\link[=link_gsf_abstract]{link_gsf_abstract()}} for the identity of the -sum of longest diameters (\code{sld}) link contribution. -} diff --git a/man/link_gsf_ttg-class.Rd b/man/link_gsf_ttg-class.Rd deleted file mode 100644 index 1aab6bc6..00000000 --- a/man/link_gsf_ttg-class.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/LinkGSF.R -\docType{class} -\name{link_gsf_ttg-class} -\alias{link_gsf_ttg-class} -\alias{.link_gsf_ttg} -\alias{link_gsf_ttg} -\title{\code{link_gsf_ttg}} -\usage{ -link_gsf_ttg(gamma = prior_normal(0, 5)) -} -\arguments{ -\item{gamma}{(\code{Prior})\cr prior for the link coefficient \code{gamma}.} -} -\description{ -This class extends the general \code{\link{link_gsf_abstract}} for the time-to-growth -(\code{ttg}) link contribution. -} diff --git a/man/show-object.Rd b/man/show-object.Rd index b19b11be..3ac46940 100644 --- a/man/show-object.Rd +++ b/man/show-object.Rd @@ -2,8 +2,8 @@ % Please edit documentation in R/DataSubject.R, R/generics.R, % R/DataLongitudinal.R, R/DataSurvival.R, R/DataJoint.R, R/StanModule.R, % R/Prior.R, R/Parameter.R, R/ParameterList.R, R/StanModel.R, -% R/Link.R, R/JointModel.R, R/Quantities.R, R/SurvivalQuantities.R, -% R/JointModelSamples.R, R/LinkNone.R, R/LongitudinalQuantities.R +% R/LinkComponent.R, R/Link.R, R/JointModel.R, R/Quantities.R, +% R/SurvivalQuantities.R, R/JointModelSamples.R, R/LongitudinalQuantities.R \name{show,DataSubject-method} \alias{show,DataSubject-method} \alias{show-object} @@ -15,12 +15,12 @@ \alias{show,Parameter-method} \alias{show,ParameterList-method} \alias{show,StanModel-method} +\alias{show,LinkComponent-method} \alias{show,Link-method} \alias{show,JointModel-method} \alias{show,Quantities-method} \alias{show,SurvivalQuantities-method} \alias{show,JointModelSamples-method} -\alias{show,LinkNone-method} \alias{show,LongitudinalQuantities-method} \title{Show an Object} \usage{ @@ -42,6 +42,8 @@ \S4method{show}{StanModel}(object) +\S4method{show}{LinkComponent}(object) + \S4method{show}{Link}(object) \S4method{show}{JointModel}(object) @@ -52,8 +54,6 @@ \S4method{show}{JointModelSamples}(object) -\S4method{show}{LinkNone}(object) - \S4method{show}{LongitudinalQuantities}(object) } \arguments{ diff --git a/man/sim_lm_random_slope.Rd b/man/sim_lm_random_slope.Rd index ac815184..d0c5152f 100644 --- a/man/sim_lm_random_slope.Rd +++ b/man/sim_lm_random_slope.Rd @@ -9,7 +9,8 @@ sim_lm_random_slope( slope_mu = c(0.01, 0.03), slope_sigma = 0.5, sigma = 2, - phi = 0.1 + link_dsld = 0, + link_identity = 0 ) } \arguments{ @@ -21,7 +22,9 @@ sim_lm_random_slope( \item{sigma}{(\code{number})\cr the variance of the longitudinal values.} -\item{phi}{(\code{number})\cr the link coefficient for the random slope contribution.} +\item{link_dsld}{(\code{number})\cr the link coefficient for the DSLD contribution.} + +\item{link_identity}{(\code{number})\cr the link coefficient for the identity contribution.} } \value{ A function with argument \code{lm_base} that can be used to simulate diff --git a/man/standard-link-methods.Rd b/man/standard-link-methods.Rd new file mode 100644 index 00000000..a2c972f0 --- /dev/null +++ b/man/standard-link-methods.Rd @@ -0,0 +1,72 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/generics.R, R/LongitudinalGSF.R, +% R/LongitudinalRandomSlope.R +\name{standard-link-methods} +\alias{standard-link-methods} +\alias{enableLink} +\alias{linkTTG} +\alias{linkDSLD} +\alias{linkIdentity} +\alias{enableLink.LongitudinalGSF} +\alias{linkDSLD.LongitudinalGSF} +\alias{linkTTG.LongitudinalGSF} +\alias{linkIdentity.LongitudinalGSF} +\alias{enableLink.LongitudinalRandomSlope} +\alias{linkDSLD.LongitudinalRandomSlope} +\alias{linkIdentity.LongitudinalRandomSlope} +\title{Standard Link Methods} +\usage{ +enableLink(object, ...) + +linkTTG(object, ...) + +linkDSLD(object, ...) + +linkIdentity(object, ...) + +\method{enableLink}{LongitudinalGSF}(object, ...) + +\method{linkDSLD}{LongitudinalGSF}(object, ...) + +\method{linkTTG}{LongitudinalGSF}(object, ...) + +\method{linkIdentity}{LongitudinalGSF}(object, ...) + +\method{enableLink}{LongitudinalRandomSlope}(object, ...) + +\method{linkDSLD}{LongitudinalRandomSlope}(object, ...) + +\method{linkIdentity}{LongitudinalRandomSlope}(object, ...) +} +\arguments{ +\item{object}{(\code{\link{StanModel}}) \cr A \code{\link{StanModel}} object.} + +\item{...}{Not used.} +} +\description{ +These generic functions enable \code{\link{LongitudinalModel}} objects to provide +their own implementations for the most common link functions. +} +\details{ +Each of these methods should return a \code{\link{StanModule}} argument that implements +the models corresponding version of that link type. +For \code{enableLink} this is called once for a model regardless of how many links +are used and its purpose is to provide the stan code to initialise any +link specific objects (to avoid clashes with each individual link function declaring +the same required stan objects). + +For further details on how to use these methods please see +\code{vignette("extending-jmpost", package = "jmpost")}. +} +\section{Functions}{ +\itemize{ +\item \code{enableLink()}: hook to include any common link code to be shared across all +link functions + +\item \code{linkTTG()}: Time to growth link + +\item \code{linkDSLD()}: Derivative of the SLD over time link + +\item \code{linkIdentity()}: Current SLD link + +}} diff --git a/man/standard-links.Rd b/man/standard-links.Rd new file mode 100644 index 00000000..f2ab3f48 --- /dev/null +++ b/man/standard-links.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/LinkComponent.R +\name{standard-links} +\alias{standard-links} +\alias{link_ttg} +\alias{link_dsld} +\alias{link_identity} +\alias{link_none} +\title{Standard Links} +\usage{ +link_ttg(prior = prior_normal(0, 2)) + +link_dsld(prior = prior_normal(0, 2)) + +link_identity(prior = prior_normal(0, 2)) + +link_none() +} +\arguments{ +\item{prior}{(\code{Prior})\cr The prior to use for the corresponding link coeficient.} +} +\description{ +These functions enable the inclusion of several common link functions in the survival model of +the joint model. + +Note that the underlying implementation of these links is specific to each longitudinal model. +} +\section{Functions}{ +\itemize{ +\item \code{link_ttg()}: Time to growth link + +\item \code{link_dsld()}: Derivative of the SLD over time link + +\item \code{link_identity()}: Current SLD link + +\item \code{link_none()}: No link (fit the survival and longitudinal models independently) + +}} diff --git a/tests/testthat/_snaps/JointModel.md b/tests/testthat/_snaps/JointModel.md index 6dbac6bb..26212c84 100644 --- a/tests/testthat/_snaps/JointModel.md +++ b/tests/testthat/_snaps/JointModel.md @@ -2,7 +2,7 @@ Code x <- JointModel(longitudinal = LongitudinalRandomSlope(), survival = SurvivalWeibullPH(), - link = LinkRandomSlope()) + link = link_dsld()) print(x) Output @@ -23,9 +23,37 @@ lm_rs_ind_rnd_slope ~ Link: - Random Slope Link with parameters: - link_lm_phi ~ normal(mu = 0.2, sigma = 0.5) + Link with the following components/parameters: + link_dsld ~ normal(mu = 0, sigma = 2) + +--- + + Code + x <- JointModel(longitudinal = LongitudinalRandomSlope(), survival = SurvivalWeibullPH(), + link = Link(link_dsld(), link_identity())) + print(x) + Output + + A Joint Model with: + + Survival: + Weibull-PH Survival Model with parameters: + sm_weibull_ph_lambda ~ gamma(alpha = 2, beta = 0.5) + sm_weibull_ph_gamma ~ gamma(alpha = 2, beta = 0.5) + beta_os_cov ~ normal(mu = 0, sigma = 5) + Longitudinal: + Random Slope Longitudinal Model with parameters: + lm_rs_intercept ~ normal(mu = 30, sigma = 10) + lm_rs_slope_mu ~ normal(mu = 0, sigma = 15) + lm_rs_slope_sigma ~ lognormal(mu = 0, sigma = 1.5) + lm_rs_sigma ~ lognormal(mu = 0, sigma = 1.5) + lm_rs_ind_rnd_slope ~ + + Link: + Link with the following components/parameters: + link_dsld ~ normal(mu = 0, sigma = 2) + link_identity ~ normal(mu = 0, sigma = 2) --- @@ -45,9 +73,8 @@ Longitudinal: Not Specified - Link: + Link: No Link - --- @@ -77,15 +104,14 @@ lm_gsf_eta_tilde_ks ~ std_normal() lm_gsf_eta_tilde_kg ~ std_normal() - Link: + Link: No Link - --- Code x <- JointModel(longitudinal = LongitudinalRandomSlope(), survival = SurvivalWeibullPH(), - link = LinkNone()) + link = Link()) print(x) Output @@ -105,7 +131,6 @@ lm_rs_sigma ~ lognormal(mu = 0, sigma = 1.5) lm_rs_ind_rnd_slope ~ - Link: + Link: No Link - diff --git a/tests/testthat/_snaps/Link.md b/tests/testthat/_snaps/Link.md new file mode 100644 index 00000000..dec393ea --- /dev/null +++ b/tests/testthat/_snaps/Link.md @@ -0,0 +1,27 @@ +# Link prints as expected + + Code + print(Link()) + Output + + No Link + +--- + + Code + print(Link(link_dsld())) + Output + + Link with the following components/parameters: + link_dsld ~ normal(mu = 0, sigma = 2) + +--- + + Code + print(Link(link_dsld(), link_identity())) + Output + + Link with the following components/parameters: + link_dsld ~ normal(mu = 0, sigma = 2) + link_identity ~ normal(mu = 0, sigma = 2) + diff --git a/tests/testthat/_snaps/LinkComponent.md b/tests/testthat/_snaps/LinkComponent.md new file mode 100644 index 00000000..5a6b23aa --- /dev/null +++ b/tests/testthat/_snaps/LinkComponent.md @@ -0,0 +1,31 @@ +# print works as expected + + Code + print(link_dsld()) + Output + + LinkComponent with parameter: + link_dsld ~ normal(mu = 0, sigma = 2) + + +--- + + Code + print(link_ttg(prior_beta(4, 1))) + Output + + LinkComponent with parameter: + link_ttg ~ beta(a = 4, b = 1) + + +--- + + Code + print(LinkComponent(stan = StanModule(), parameters = ParameterList(Parameter( + prior = prior_normal(0, 5), name = "bob", size = 1)), key = "link_bob")) + Output + + LinkComponent with parameter: + bob ~ normal(mu = 0, sigma = 5) + + diff --git a/tests/testthat/_snaps/LinkGSF.md b/tests/testthat/_snaps/LinkGSF.md deleted file mode 100644 index f72c4043..00000000 --- a/tests/testthat/_snaps/LinkGSF.md +++ /dev/null @@ -1,25 +0,0 @@ -# Print method for LinkGSF works as expected - - Code - x <- LinkGSF() - print(x) - Output - - GSF (dSLD + TTG) Link with parameters: - lm_gsf_beta ~ normal(mu = 0, sigma = 5) - lm_gsf_gamma ~ normal(mu = 0, sigma = 5) - - ---- - - Code - x <- LinkGSF(link_gsf_identity(tau = prior_cauchy(1, 2)), link_gsf_dsld(beta = prior_gamma( - 2, 2))) - print(x) - Output - - GSF (Identity + dSLD) Link with parameters: - lm_gsf_tau ~ cauchy(mu = 1, sigma = 2) - lm_gsf_beta ~ gamma(alpha = 2, beta = 2) - - diff --git a/tests/testthat/_snaps/LinkNone.md b/tests/testthat/_snaps/LinkNone.md deleted file mode 100644 index 7bdc4927..00000000 --- a/tests/testthat/_snaps/LinkNone.md +++ /dev/null @@ -1,9 +0,0 @@ -# Print method for LinkNone works as expected - - Code - x <- LinkNone() - print(x) - Output - No Link - - diff --git a/tests/testthat/_snaps/LinkRandomSlope.md b/tests/testthat/_snaps/LinkRandomSlope.md deleted file mode 100644 index 1d38fe5c..00000000 --- a/tests/testthat/_snaps/LinkRandomSlope.md +++ /dev/null @@ -1,22 +0,0 @@ -# Print method for LinkRandomSlope works as expected - - Code - x <- LinkRandomSlope() - print(x) - Output - - Random Slope Link with parameters: - link_lm_phi ~ normal(mu = 0.2, sigma = 0.5) - - ---- - - Code - x <- LinkRandomSlope(link_lm_phi = prior_normal(0, 1)) - print(x) - Output - - Random Slope Link with parameters: - link_lm_phi ~ normal(mu = 0, sigma = 1) - - diff --git a/tests/testthat/helper-example_data.R b/tests/testthat/helper-example_data.R index afccde66..1dd36a88 100644 --- a/tests/testthat/helper-example_data.R +++ b/tests/testthat/helper-example_data.R @@ -17,7 +17,7 @@ ensure_test_data_1 <- function() { sigma = 3, slope_mu = c(1, 3), slope_sigma = 0.2, - phi = 0 + link_dsld = 0 ), os_fun = sim_os_exponential(1 / 100), .debug = TRUE, diff --git a/tests/testthat/helper-setup.R b/tests/testthat/helper-setup.R index 837985aa..14d84659 100644 --- a/tests/testthat/helper-setup.R +++ b/tests/testthat/helper-setup.R @@ -9,3 +9,28 @@ CACHE_DIR <- if (Sys.getenv("JMPOST_CACHE_DIR") == "") { is_full_test <- function() { toupper(Sys.getenv("JMPOST_FULL_TEST")) == "TRUE" } + + +# Runs stans syntax parser and will throw an error if stan detects an +# issue that would prevent compilation +expect_stan_syntax <- function(code) { + model_obj <- cmdstanr::cmdstan_model( + cmdstanr::write_stan_file(as.character(code)), + compile = FALSE + ) + expect_true(model_obj$check_syntax(quiet = TRUE)) +} + + +# Load multiple `inst` stan files including the base functions file (as many of them +# depend on this) +load_with_base_stan <- function(...) { + sm <- Reduce( + merge, + lapply(list(...), StanModule) + ) + merge( + sm, + StanModule("base/functions.stan") + ) +} diff --git a/tests/testthat/models/gsf_identity_link.stan b/tests/testthat/models/gsf_identity_link.stan index ffd8dba8..d9587dba 100644 --- a/tests/testthat/models/gsf_identity_link.stan +++ b/tests/testthat/models/gsf_identity_link.stan @@ -8,12 +8,22 @@ data { vector[sld_n] sld_phi; } + +transformed data { + // + // Source - lm-gsf/link.stan + // + matrix[sld_n, 4] link_function_inputs; + link_function_inputs[,1] = sld_bsld; + link_function_inputs[,2] = sld_s; + link_function_inputs[,3] = sld_g; + link_function_inputs[,4] = sld_phi; +} + + generated quantities { - matrix[sld_n, time_p] results = link_identity_contribution( + matrix[sld_n, time_p] results = link_identity_contrib( sld_time, - sld_bsld, - sld_s, - sld_g, - sld_phi + link_function_inputs ); } diff --git a/tests/testthat/test-JointModel.R b/tests/testthat/test-JointModel.R index 17a998bd..6e8d60b8 100644 --- a/tests/testthat/test-JointModel.R +++ b/tests/testthat/test-JointModel.R @@ -6,7 +6,7 @@ test_that("JointModel smoke tests", { jm <- JointModel( longitudinal = LongitudinalRandomSlope(), survival = SurvivalWeibullPH(), - link = LinkRandomSlope() + link = link_dsld() ) jm_char <- as.character(jm) @@ -20,7 +20,20 @@ test_that("JointModel print method works as expected", { x <- JointModel( longitudinal = LongitudinalRandomSlope(), survival = SurvivalWeibullPH(), - link = LinkRandomSlope() + link = link_dsld() + ) + print(x) + }) + + + expect_snapshot({ + x <- JointModel( + longitudinal = LongitudinalRandomSlope(), + survival = SurvivalWeibullPH(), + link = Link( + link_dsld(), + link_identity() + ) ) print(x) }) @@ -43,7 +56,7 @@ test_that("JointModel print method works as expected", { x <- JointModel( longitudinal = LongitudinalRandomSlope(), survival = SurvivalWeibullPH(), - link = LinkNone() + link = Link() ) print(x) }) diff --git a/tests/testthat/test-JointModelSamples.R b/tests/testthat/test-JointModelSamples.R index a4f7ac39..78134bda 100644 --- a/tests/testthat/test-JointModelSamples.R +++ b/tests/testthat/test-JointModelSamples.R @@ -10,8 +10,7 @@ test_that("smoke test for JointModelSamples", { intercept = 30, sigma = 3, slope_mu = c(1, 3), - slope_sigma = 0.2, - phi = 0 + slope_sigma = 0.2 ), os_fun = sim_os_exponential(1 / 100), .debug = TRUE, diff --git a/tests/testthat/test-Link.R b/tests/testthat/test-Link.R new file mode 100644 index 00000000..88bdd33b --- /dev/null +++ b/tests/testthat/test-Link.R @@ -0,0 +1,84 @@ + + + +test_that("link_none() works as expected", { + expect_equal( + Link(), + link_none() + ) + + expect_equal( + length(Link()), + 0 + ) + + + # Double check that key "link_none" bits of stan code present in the complete joint model + # if no link is provided + x <- JointModel( + longitudinal = LongitudinalRandomSlope(), + survival = SurvivalWeibullPH(), + link = Link() + ) + expect_true( + grepl( + "return rep_matrix(0, rows(time), cols(time));", + as.character(x), + fixed = TRUE + ) + ) + expect_true( + grepl( + "matrix[Nind, 0] link_function_inputs = rep_matrix(0, Nind, 0);", + as.character(x), + fixed = TRUE + ) + ) +}) + + +test_that("Link works as expected", { + x <- Link( + link_dsld(prior_gamma(8, 2)), + link_identity() + ) + + expect_equal( + length(x), + 2 + ) + + expect_equal( + getParameters(x), + ParameterList( + Parameter(prior_gamma(8, 2), "link_dsld", 1), + Parameter(prior_normal(0, 2), "link_identity", 1) + ) + ) + + ## Fully resolved stan code pass's the syntax checker + expect_stan_syntax( + merge( + load_with_base_stan("lm-gsf/functions.stan"), + as.StanModule(x, model = LongitudinalGSF()) + ) + ) + + # Check that function is idempotant + expect_equal( + Link(Link(link_dsld())), + Link(link_dsld()) + ) +}) + +test_that("Link prints as expected", { + expect_snapshot( + print(Link()) + ) + expect_snapshot( + print(Link(link_dsld())) + ) + expect_snapshot( + print(Link(link_dsld(), link_identity())) + ) +}) diff --git a/tests/testthat/test-LinkComponent.R b/tests/testthat/test-LinkComponent.R new file mode 100644 index 00000000..cdeca248 --- /dev/null +++ b/tests/testthat/test-LinkComponent.R @@ -0,0 +1,143 @@ + + +test_that("all link files pass stan's syntax checker", { + expect_stan_syntax( + load_with_base_stan("lm-gsf/link_dsld.stan") + ) + expect_stan_syntax( + load_with_base_stan("lm-gsf/link_ttg.stan") + ) + expect_stan_syntax( + load_with_base_stan("lm-gsf/link_identity.stan", "lm-gsf/functions.stan") + ) + + + expect_stan_syntax( + load_with_base_stan("lm-random-slope/link_dsld.stan") + ) + expect_stan_syntax( + load_with_base_stan("lm-random-slope/link_identity.stan") + ) +}) + + + +test_that("complete models with links pass stan's syntax checker", { + + x <- JointModel( + longitudinal = LongitudinalRandomSlope(), + survival = SurvivalWeibullPH(), + link = Link(link_dsld(), link_identity()) + ) + stan <- as.StanModule(x) + # Currently generated quantities depends on data elements that aren't defined + # Until later so void it out for now + stan@generated_quantities <- "" + expect_stan_syntax(stan) + + + + x <- JointModel( + longitudinal = LongitudinalGSF(), + survival = SurvivalLogLogistic(), + link = Link(link_dsld(), link_identity(), link_ttg()) + ) + stan <- as.StanModule(x) + # Currently generated quantities depends on data elements that aren't defined + # Until later so void it out for now + stan@generated_quantities <- "" + expect_stan_syntax(stan) + +}) + + +test_that("LinkComponents are constructed correctly and can access key components", { + par_list <- ParameterList( + Parameter(prior = prior_normal(0, 5), name = "bobby", size = 1) + ) + + x <- LinkComponent( + stan = \(model) { + expect_class(model, "LongitudinalGSF") + StanModule("lm-gsf/link_ttg.stan") + }, + key = "bob", + parameters = par_list + ) + + # Check that `stan` function arguments are called correctly + expect_equal( + as.StanModule(x, model = LongitudinalGSF()), + StanModule("lm-gsf/link_ttg.stan") + ) + + expect_equal( + getParameters(x), + par_list + ) + + expect_equal( + x@key, + "bob" + ) +}) + + +test_that("Model specific links return the correct stan code", { + + ### GSF + + expect_equal( + as.StanModule(link_dsld(), model = LongitudinalGSF()), + StanModule("lm-gsf/link_dsld.stan") + ) + + expect_equal( + as.StanModule(link_identity(), model = LongitudinalGSF()), + StanModule("lm-gsf/link_identity.stan") + ) + + expect_equal( + as.StanModule(link_ttg(), model = LongitudinalGSF()), + StanModule("lm-gsf/link_ttg.stan") + ) + + ### Random Slope + + expect_equal( + as.StanModule(link_dsld(), model = LongitudinalRandomSlope()), + StanModule("lm-random-slope/link_dsld.stan") + ) + + expect_equal( + as.StanModule(link_identity(), model = LongitudinalRandomSlope()), + StanModule("lm-random-slope/link_identity.stan") + ) + + expect_error( + as.StanModule(link_ttg(), model = LongitudinalRandomSlope()), + regexp = "TTG link is not available" + ) + +}) + + +test_that("print works as expected", { + expect_snapshot( + print(link_dsld()) + ) + expect_snapshot( + print(link_ttg(prior_beta(4, 1))) + ) + expect_snapshot( + print( + LinkComponent( + stan = StanModule(), + parameters = ParameterList( + Parameter(prior = prior_normal(0, 5), name = "bob", size = 1) + ), + key = "link_bob" + ) + ) + ) +}) diff --git a/tests/testthat/test-LinkGSF.R b/tests/testthat/test-LinkGSF.R deleted file mode 100644 index 4bd256b1..00000000 --- a/tests/testthat/test-LinkGSF.R +++ /dev/null @@ -1,72 +0,0 @@ -# LinkGSF-class ---- - -test_that("LinkGSF class initialization works as expected", { - result <- expect_silent(.LinkGSF()) - expect_s4_class(result, "LinkGSF") -}) - -# LinkGSF-constructors ---- - -test_that("LinkGSF user constructor works as expected with defaults", { - result <- expect_silent(LinkGSF()) - expect_s4_class(result, "LinkGSF") -}) - -# link_gsf_ttg-constructors ---- - -test_that("link_gsf_ttg user constructor works as expected with defaults", { - result <- expect_silent(link_gsf_ttg()) - expect_s4_class(result, "link_gsf_ttg") -}) - -# link_gsf_dsld-constructors ---- - -test_that("link_gsf_dsld user constructor works as expected with defaults", { - result <- expect_silent(link_gsf_dsld()) - expect_s4_class(result, "link_gsf_dsld") -}) - - -# link_gsf_identity-constructors ---- - -test_that("link_gsf_identity user constructor works as expected with defaults", { - result <- expect_silent(link_gsf_identity()) - expect_s4_class(result, "link_gsf_identity") -}) - - -test_that("LinkGSF returns correct defaults when no arguments are supplied", { - - get_par_names <- function(x) { - vapply(x@parameters@parameters, function(x) x@name, character(1)) - } - - link <- LinkGSF() - par_names <- get_par_names(link) - expect_equal(par_names, c("lm_gsf_beta", "lm_gsf_gamma")) - - link <- LinkGSF(link_gsf_ttg()) - par_names <- get_par_names(link) - expect_equal(par_names, c("lm_gsf_gamma")) - - link <- LinkGSF(link_gsf_identity()) - par_names <- get_par_names(link) - expect_equal(par_names, c("lm_gsf_tau")) -}) - - -test_that("Print method for LinkGSF works as expected", { - - expect_snapshot({ - x <- LinkGSF() - print(x) - }) - - expect_snapshot({ - x <- LinkGSF( - link_gsf_identity(tau = prior_cauchy(1, 2)), - link_gsf_dsld(beta = prior_gamma(2, 2)) - ) - print(x) - }) -}) diff --git a/tests/testthat/test-LinkNone.R b/tests/testthat/test-LinkNone.R deleted file mode 100644 index 632f0e20..00000000 --- a/tests/testthat/test-LinkNone.R +++ /dev/null @@ -1,20 +0,0 @@ -test_that("LinkNone object is generated without unexpected input", { - expect_error( - LinkNone(stan = StanModule(), parameters = ParameterList(mu = 0.2)), - "unused argument", - ignore.case = TRUE - ) - link <- LinkNone() - expect_true(is(link, "Link")) - expect_true(is(link, "LinkNone")) - expect_false(is(link, "LongitudinalModel")) - expect_false(is(link, "link_gsf_abstract")) -}) - - -test_that("Print method for LinkNone works as expected", { - expect_snapshot({ - x <- LinkNone() - print(x) - }) -}) diff --git a/tests/testthat/test-LinkRandomSlope.R b/tests/testthat/test-LinkRandomSlope.R deleted file mode 100644 index 55fda0f3..00000000 --- a/tests/testthat/test-LinkRandomSlope.R +++ /dev/null @@ -1,36 +0,0 @@ -test_that("LinkRandomSlope smoke tests", { - linkobject <- LinkRandomSlope( - link_lm_phi = prior_normal(0, 5) - ) - iv <- with_mocked_bindings( - initialValues(linkobject, n_chains = 2), - local_rnorm = \(...) 5 - ) - expect_equal( - iv, - list( - list("link_lm_phi" = 5 / 2), - list("link_lm_phi" = 5 / 2) - ) - ) - expect_true( - is(as.StanModule(linkobject), "StanModule") - ) - expect_error( - LongitudinalRandomSlope(lm_rs_pp = prior_normal(0, 1)), - "unused argument \\(lm_rs_pp" - ) -}) - -test_that("Print method for LinkRandomSlope works as expected", { - - expect_snapshot({ - x <- LinkRandomSlope() - print(x) - }) - - expect_snapshot({ - x <- LinkRandomSlope(link_lm_phi = prior_normal(0, 1)) - print(x) - }) -}) diff --git a/tests/testthat/test-LongitudinalGSF.R b/tests/testthat/test-LongitudinalGSF.R index 948b7679..3d338841 100644 --- a/tests/testthat/test-LongitudinalGSF.R +++ b/tests/testthat/test-LongitudinalGSF.R @@ -25,7 +25,7 @@ test_that("Print method for LongitudinalGSF works as expected", { test_that("Centralised parameterisation compiles without issues", { - jm <- JointModel(longitudinal = LongitudinalGSF(centered = TRUE)) + jm <- JointModel(longitudinal = LongitudinalGSF(centred = TRUE)) expect_false(any( c("lm_gsf_eta_tilde_kg", "lm_gsf_eta_tilde_bsld") %in% names(jm@parameters) )) @@ -37,7 +37,7 @@ test_that("Centralised parameterisation compiles without issues", { test_that("Non-Centralised parameterisation compiles without issues", { - jm <- JointModel(longitudinal = LongitudinalGSF(centered = FALSE)) + jm <- JointModel(longitudinal = LongitudinalGSF(centred = FALSE)) expect_true(all( c("lm_gsf_eta_tilde_kg", "lm_gsf_eta_tilde_bsld") %in% names(jm@parameters) )) @@ -117,14 +117,14 @@ test_that("Can recover known distributional parameters from a full GSF joint mod a_phi = prior_lognormal(log(6), 1), b_phi = prior_lognormal(log(8), 1), sigma = prior_lognormal(log(0.01), 1), - centered = TRUE + centred = TRUE ), survival = SurvivalExponential( lambda = prior_lognormal(log(1 / (400 / 365)), 1) ), - link = LinkGSF( - link_gsf_dsld(), - link_gsf_ttg() + link = Link( + link_dsld(), + link_ttg() ) ) diff --git a/tests/testthat/test-SurvivalExponential.R b/tests/testthat/test-SurvivalExponential.R index a89a7751..4e8aa94f 100644 --- a/tests/testthat/test-SurvivalExponential.R +++ b/tests/testthat/test-SurvivalExponential.R @@ -10,7 +10,7 @@ test_that("SurvivalExponential can recover true parameter (no covariates)", { lambda_cen = 1 / 9000, beta_cat = c("A" = 0, "B" = 0, "C" = 0), beta_cont = 0, - lm_fun = sim_lm_random_slope(phi = 0, slope_mu = 0), + lm_fun = sim_lm_random_slope(link_dsld = 0, slope_mu = 0), os_fun = sim_os_exponential(lambda = true_lambda) ) @@ -63,7 +63,7 @@ test_that("SurvivalExponential can recover true parameter (including covariates) lambda_cen = 1 / 9000, beta_cat = c("A" = 0, "B" = true_beta[1], "C" = true_beta[2]), beta_cont = true_beta[3], - lm_fun = sim_lm_random_slope(phi = 0, slope_mu = 0), + lm_fun = sim_lm_random_slope(link_dsld = 0, slope_mu = 0), os_fun = sim_os_exponential(lambda = true_lambda) ) diff --git a/tests/testthat/test-brierScore.R b/tests/testthat/test-brierScore.R index 723e9d3a..288da157 100644 --- a/tests/testthat/test-brierScore.R +++ b/tests/testthat/test-brierScore.R @@ -24,7 +24,7 @@ test_that("brierScore(SurvivalQuantities) returns same results as survreg", { "C" = 0.5 ), beta_cont = 0.2, - lm_fun = sim_lm_random_slope(phi = 0), + lm_fun = sim_lm_random_slope(), os_fun = sim_os_exponential(lambda = 1 / 100) ) diff --git a/tests/testthat/test-initialValues.R b/tests/testthat/test-initialValues.R index 8a6332cb..415f4163 100644 --- a/tests/testthat/test-initialValues.R +++ b/tests/testthat/test-initialValues.R @@ -4,7 +4,7 @@ test_that("initialValues() works as expected", { jm <- JointModel( longitudinal = LongitudinalRandomSlope(), survival = SurvivalWeibullPH(), - link = LinkRandomSlope() + link = link_dsld() ) set.seed(341) @@ -28,8 +28,8 @@ test_that("initialValues() works as expected", { expect_equal( c( "lm_rs_intercept", "lm_rs_slope_mu", "lm_rs_slope_sigma", "lm_rs_sigma", - "lm_rs_ind_rnd_slope", "link_lm_phi", "sm_weibull_ph_lambda", - "sm_weibull_ph_gamma", "beta_os_cov" + "lm_rs_ind_rnd_slope", "sm_weibull_ph_lambda", + "sm_weibull_ph_gamma", "beta_os_cov", "link_dsld" ), names(initial_values[[1]]) ) @@ -86,7 +86,7 @@ test_that("ensure_initial_values() works as expected", { test_that("intial values for fixed distributions gives valid values", { set.seed(3150) - gsfmodel <- LongitudinalGSF(centered = TRUE) + gsfmodel <- LongitudinalGSF(centred = TRUE) ivs <- initialValues(gsfmodel, n_chains = 100) for (values in ivs) { diff --git a/tests/testthat/test-misc_models.R b/tests/testthat/test-misc_models.R index caaf510e..f1479f21 100644 --- a/tests/testthat/test-misc_models.R +++ b/tests/testthat/test-misc_models.R @@ -17,7 +17,7 @@ test_that("Longitudinal Model doesn't print sampler rejection messages", { times = 1:1000, lambda_cen = 1 / 500, n = c(200, 200), - lm_fun = sim_lm_random_slope(phi = 0), + lm_fun = sim_lm_random_slope(), os_fun = sim_os_exponential(1 / 100) ) diff --git a/tests/testthat/test-model_random_slope.R b/tests/testthat/test-model_random_slope.R index 78706232..e8b89ef2 100644 --- a/tests/testthat/test-model_random_slope.R +++ b/tests/testthat/test-model_random_slope.R @@ -11,8 +11,7 @@ test_that("Random Slope Model can recover known parameter values", { intercept = 30, sigma = 3, slope_mu = c(1, 3), - slope_sigma = 0.2, - phi = 0 + slope_sigma = 0.2 ), os_fun = sim_os_exponential(1 / 100), .debug = TRUE, diff --git a/tests/testthat/test-model_random_slope_2chain.R b/tests/testthat/test-model_random_slope_2chain.R index 85c35aa5..0e5e31b4 100644 --- a/tests/testthat/test-model_random_slope_2chain.R +++ b/tests/testthat/test-model_random_slope_2chain.R @@ -3,7 +3,7 @@ test_that("Can recover known distribution parameters from random slope model whe jm <- JointModel( longitudinal = LongitudinalRandomSlope(), survival = SurvivalExponential(), - link = LinkRandomSlope() + link = link_dsld() ) set.seed(3251) @@ -22,7 +22,7 @@ test_that("Can recover known distribution parameters from random slope model whe sigma = 3, slope_mu = c(1, 3), slope_sigma = 0.2, - phi = 0.1 + link_dsld = 0.1 ), os_fun = sim_os_exponential(lambda = 1 / 200) ) @@ -69,7 +69,7 @@ test_that("Can recover known distribution parameters from random slope model whe "lm_rs_slope_mu[1]" = 1, "lm_rs_slope_mu[2]" = 3, "lm_rs_slope_sigma" = 0.2, - "link_lm_phi" = 0.1 + "link_dsld" = 0.1 ) results_df <- mp@results$summary(names(vars)) diff --git a/tests/testthat/test-simulations.R b/tests/testthat/test-simulations.R index 2f521ad1..8a580072 100644 --- a/tests/testthat/test-simulations.R +++ b/tests/testthat/test-simulations.R @@ -48,7 +48,7 @@ test_that("sim_lm_random_slope works as expected", { slope_mu = c(0.01, 0.03), slope_sigma = 0.5, sigma = 2, - phi = 0.1 + link_dsld = 0.1 )) expect_true(is.function(result)) set.seed(123) @@ -151,7 +151,7 @@ test_that("sim_os_exponential creates a dataset with the correct parameter", { times = seq(1, 1000), beta_cont = 0, beta_cat = c("A" = 0, "B" = 0, "C" = 0), - lm_fun = sim_lm_random_slope(phi = 0), + lm_fun = sim_lm_random_slope(link_dsld = 0), os_fun = sim_os_exponential(lambda = 1 / 100) ) osdat <- sim_data$os @@ -175,7 +175,7 @@ test_that("sim_os_weibull creates a dataset with the correct parameter", { times = seq(1, 1000), beta_cont = 0.2, beta_cat = c("A" = 0, "B" = -0.6, "C" = 0.6), - lm_fun = sim_lm_random_slope(phi = 0), # No link + lm_fun = sim_lm_random_slope(link_dsld = 0), # No link os_fun = sim_os_weibull(lambda = lambda_real, gamma = gamma_real), .silent = TRUE ) diff --git a/vignettes/extending-jmpost.Rmd b/vignettes/extending-jmpost.Rmd index 0b3b6a42..aff43029 100644 --- a/vignettes/extending-jmpost.Rmd +++ b/vignettes/extending-jmpost.Rmd @@ -28,6 +28,167 @@ library(jmpost) Please note that this document is currently a work-in-progress and does not contain complete information for this package yet. + +## Custom Link Functions + +Users can define custom link functions in several ways based upon the level of customisation +required. In order to explain this process it is first important to understand how the link +functions are implemented under the hood. + +The link functions add their contribution to the likelihood function via the log-hazard function; +that is the general model is formulated as: + +$$ +log(h_i(t, \phi_i)) = log(h_0(t)) + X_i \beta + \alpha_1 f(t, \phi_i) + \alpha_2 g(t, \phi_i) + \dots +$$ + +Where: +- $X$ is the design matrix of covariates +- $\beta$ is the vector of coefficients for the covariates +- $f(t)$ and $g(t)$ are the link functions +- $\alpha_1$ and $\alpha_2$ are the coefficients for the link functions +- $h_0(t)$ is the baseline hazard function +- $\phi_i$ is a vector of parameters from the longitudinal model + +Each longitudinal model is responsible for defining their own implementations of the $\phi$ vector. +The interface for doing this is by providing an `enableLink` method that updates the `StanModule` +object of the model to define a Stan matrix with the name `link_function_inputs` +that contains 1 row per +patient and 1 column per $\phi$ parameter. + +For reference the following is roughly the implementation for the `LongitudinalGSF` model: + +```R +enableLink.LongitudinalGSF <- function(object, ...) { + stan <- StanModule(" +transformed parameters { + matrix[Nind, 4] link_function_inputs; + link_function_inputs[,1] = lm_gsf_psi_bsld; + link_function_inputs[,2] = lm_gsf_psi_ks; + link_function_inputs[,3] = lm_gsf_psi_kg; + link_function_inputs[,4] = lm_gsf_psi_phi; +}") + object@stan <- merge( + object@stan, + stan + ) + object +} +``` +That is to say the $\phi$ parameters for the `LongitudinalGSF` model are the 4 primary parameters +of the longitudinal model. If you wish to augment this with additional parameters then you can +subclass the `LongitudinalGSF` model and override the `enableLink` method to the required +additional parameters e.g. + +```R +GSFextended <- setClass( + Class = "GSFextended", + contains = "LongitudinalGSF" +) + +enableLink.GSFextended <- function(object, ...) { + stan <- StanModule("") + object@stan <- merge( + object@stan, + stan + ) + object +} +``` + +Next, the individual link functions are implemented as Stan functions with the following signature: + +``` +matrix _contrib( + matrix time, + matrix link_function_inputs +) +``` +Where: +- `` is the name of the link parameter as specified in the `LinkComponent` object +- `time` is a matrix of 1 row per patient and 1 column per time point to be evaluated at for +that patient. + +The `LinkComponent` object is responsible for then integrating these functions +into the final Stan model. For reference the following is roughly the implementation of the +dSLD link for the `LongitudinalRandomSlope` model: + +```R +LinkComponent( + key = "link_dsld", + stan = StanModule(" +functions { + matrix link_dsld_contrib( + matrix time, + matrix link_function_inputs + ) { + int nrows = rows(time); + int ncols = cols(time); + vector[nrows] lm_rs_ind_rnd_slope = link_function_inputs[,2]; + matrix[nrows, ncols] rnd_slope_mat = rep_matrix(lm_rs_ind_rnd_slope, ncols); + return rnd_slope_mat; + } +}"), + parameters = ParameterList(Parameter(name = "link_dsld", prior = prior, size = 1)) +) +``` + +You can then pass these `LinkComponent` objects to the `link` argument of the `JointModel` +constructor to add them to the model. If you wish to add multiple link functions then you must +wrap them in a `Link()` object e.g. + +```R +Link( + LinkComponent(...), + LinkComponent(...) +) +``` + +Note that there are a few families of link functions that are common across all models for example +identity, dSLD, TTG. Users can access these by simply using the inbuilt `link_identity()`, +`link_dsld()` and `link_ttg()` functions respectively. +These functions are responsible for loading the correct `LinkComponent` to +implement that link for a particular model. +That is if the user wants to specify both the +TTG link and the dSLD for a model then they can do so via: + +```R +JointModel( + ..., + link = Link( + link_dsld(), + link_ttg() + ) +) +``` + +Under the hood these link-family functions work by defining a `LinkComponent` object +that specifies a method that will return a `StanModule` object instead of specifying +the `StanModule` object directly. This method will then be called by the `JointModel` constructor +with the `LongitudinalModel` object as its first argument. This way each longitudinal model is +able to specify their own implementation for the given link family. The following is a rough outline +for how this works for the `link_dsld()` function and the `LongitudinalGSF` model: + +```R +link_dsld <- function() { + LinkComponent( + key = "link_dsld", + stan = linkDSLD, + parameters = ParameterList(Parameter(name = "link_dsld", prior = prior, size = 1)) + ) +} + +linkDSLD.LongitudinalGSF <- function(object, ...) { + StanModule("") +} +``` + +This is to say that if you wish to overwrite a model implementation for an existing link family or +provide an implementation for a new model then you can do so by defining/overwriting the relevant +methods. For clarity the underlying methods are `linkDSLD`, `linkIdentity` and `linkTTG` +respectively. + + ## Formatting Stan Files Under the hood this library works by merging multiple Stan programs together into a single diff --git a/vignettes/model_fitting.Rmd b/vignettes/model_fitting.Rmd index e82fcfac..c8369c43 100644 --- a/vignettes/model_fitting.Rmd +++ b/vignettes/model_fitting.Rmd @@ -42,8 +42,8 @@ Let's first specify a very simple joint model with: ```{r simple_model} simple_model <- JointModel( longitudinal = LongitudinalRandomSlope(), - link = LinkRandomSlope(), - survival = SurvivalWeibullPH() + survival = SurvivalWeibullPH(), + link = link_dsld() ) ``` @@ -81,13 +81,13 @@ the prior for the `slope_mu` parameter to be a $N(10, 2)$ distribution. ## Separate Models It is also possible to not link the longitudinal and the survival models, by using -the special `LinkNone` link specification. For example, +the special `link_none()` link specification. For example, ```{r simple_model_no_link} simple_model_no_link <- JointModel( longitudinal = LongitudinalRandomSlope(), - link = LinkNone(), - survival = SurvivalWeibullPH() + survival = SurvivalWeibullPH(), + link = link_none() ) ``` @@ -142,7 +142,7 @@ sim_data <- simulate_joint_data( slope_mu = c(1, 2), slope_sigma = 0.2, sigma = 20, - phi = 0.1 + link_dsld = 0.1 ), os_fun = sim_os_weibull( lambda = 1 / 300, @@ -270,7 +270,7 @@ vars <- c( "lm_rs_slope_mu", "lm_rs_slope_sigma", "lm_rs_sigma", - "link_lm_phi", + "link_dsld", "sm_weibull_ph_lambda", "sm_weibull_ph_gamma", "beta_os_cov" @@ -394,9 +394,9 @@ For example: ```r joint_model <- JointModel( - LongitudinalRandomSlope(), - SurvivalExponential(), - LinkNone() + longitudinal = LongitudinalRandomSlope(), + survival = SurvivalExponential(), + link = link_none() ) initial_values <- initialValues(joint_model, n_chains = 2)