Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework link functions #259

Merged
merged 19 commits into from
Feb 22, 2024
Merged

Rework link functions #259

merged 19 commits into from
Feb 22, 2024

Conversation

gowerc
Copy link
Collaborator

@gowerc gowerc commented Feb 14, 2024

Closes #238

I think I've finished the core functionality but still need to re-do the docs and unit tests. Was just hoping to share to see if there was any initial concerns / feedback.

Theres a lot going on in this PR so the below attempts to highlight the core components + how it works:

In terms of the UI the end users would now call the modelling functions like:

jm <- JointModel(
    longitudinal = LongitudinalRandomSlope(),
    survival = SurvivalExponential(),
    link = link_dsld()
)

Or if they have multiple links:

jm <- JointModel(
    longitudinal = LongitudinalRandomSlope(),
    survival = SurvivalExponential(),
    link = Link(
        link_dsld(),
        link_identity(prior = prior_normal(0, 0.02))
    )
)

Going one layer down, the workhorse object is the LinkComponent. The idea is these would be exported so users can define their own link code if needed:

link_bob <- function(prior = prior_normal(0, 2)) {
    LinkComponent(
        key = "link_bob",
        stan = StanModule("<some stan code here>"),
        parameter = ParameterList(Parameter(name = "link_bob", prior = prior, size = 1))
    )
}

In terms of the required stan code, the current interface for creating your own link is a stan function of the signature:

matrix <key>_contrib(
        matrix time,
        matrix link_function_inputs
)

Where link_function_inputs is an arbitrary matrix that is defined by the longitudinal model. For example for the GSF model it is defined as:

    matrix[Nind, 4] link_function_inputs;
    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;

Technically it is possible for users to extend the link_function_inputs matrix from the default though the process of doing so is a little involved. That is, under the hood the longitudinal models define this Stan code by implementing the enableLink() method, so users can subclass the model and then implement their own enableLink() method to override the default. For reference this is the current enableLink for the RandomSlope model:

enableLink.LongitudinalRandomSlope <- function(object, ...) {
    object@stan <- merge(
        object@stan,
        StanModule("lm-random-slope/link.stan")
    )
    object
}

In terms of our implementation of the common links (e.g. dsld/ttg) we allow the stan argument to LinkComponent to be a method which will be called against the Longitudinal model object allowing the models to provide their own implementations of each link. For example the actual link_dsld() function is:

#' @export
link_dsld <- function(prior = prior_normal(0, 2)) {
    LinkComponent(
        key = "link_dsld",
        stan = linkDSLD,
        parameter = ParameterList(Parameter(name = "link_dsld", prior = prior, size = 1))
    )
}

And then we implement model specific methods to provide individual model implementations e.g.

linkDSLD.LongitudinalRandomSlope <- function(object, ...) {
    StanModule("lm-random-slope/link_dsld.stan")
}

And finally as all models subclass from the base LongitudinalModel class I've also provided default implementations to throw meaningful errors if a model doesn't have a method defined e.g.

linkDSLD.LongitudinalModel <- function(object, ...) {
    stop(
        sprintf(
            "DSLD link is not available for the %s model",
            object@name
        )
    )
}

@danielinteractive
Copy link
Collaborator

Just read through the description, and looks very good to me @gowerc !

@gowerc
Copy link
Collaborator Author

gowerc commented Feb 14, 2024

Just added my first attempt at the documentation pages, though still need to add a general "extending jmpost vignette"

@gowerc gowerc changed the title WIP - Rework link functions Rework link functions Feb 16, 2024
@gowerc gowerc marked this pull request as ready for review February 16, 2024 17:45
@gowerc
Copy link
Collaborator Author

gowerc commented Feb 16, 2024

Ok I think this is 95% done now.

Only things outstanding that I still need to add are:

  • Print method for the link component object
  • Vignette outlining how to add your own link functions

Copy link
Contributor

github-actions bot commented Feb 16, 2024

badge

Code Coverage Summary

Filename                       Stmts    Miss  Cover    Missing
---------------------------  -------  ------  -------  --------------------------------
R/brier_score.R                  166       0  100.00%
R/DataJoint.R                     76       2  97.37%   264, 270
R/DataLongitudinal.R             119       1  99.16%   245
R/DataSubject.R                   69       1  98.55%   124
R/DataSurvival.R                  74       1  98.65%   146
R/defaults.R                      10       6  40.00%   17-56, 83
R/generics.R                      19       1  94.74%   49
R/JointModel.R                   122       8  93.44%   142-144, 194, 198, 240, 286, 292
R/JointModelSamples.R             54       0  100.00%
R/Link.R                          55       4  92.73%   159-162
R/LinkComponent.R                 47       5  89.36%   100, 118, 132-149
R/LongitudinalGSF.R               64       0  100.00%
R/LongitudinalModel.R             35      12  65.71%   68-83
R/LongitudinalQuantities.R        85       8  90.59%   100-107
R/LongitudinalRandomSlope.R       27       0  100.00%
R/Parameter.R                     14       0  100.00%
R/ParameterList.R                 42       1  97.62%   184
R/Prior.R                        236       8  96.61%   480, 576, 588-606
R/Quantities.R                   105       0  100.00%
R/settings.R                      11      11  0.00%    40-53
R/simulations_gsf.R               41       0  100.00%
R/simulations_os.R                11       5  54.55%   35-39
R/simulations_rs.R                21       0  100.00%
R/simulations.R                   99       1  98.99%   122
R/StanModel.R                     15       0  100.00%
R/StanModule.R                   177       6  96.61%   199-200, 242, 253, 388, 416
R/SurvivalExponential.R           10       0  100.00%
R/SurvivalLoglogistic.R           11       0  100.00%
R/SurvivalModel.R                 19       0  100.00%
R/SurvivalQuantities.R           151       6  96.03%   173-178
R/SurvivalWeibullPH.R             11       0  100.00%
R/utilities.R                    145       1  99.31%   13
R/zzz.R                            2       2  0.00%    3-12
TOTAL                           2143      90  95.80%

Diff against main

Filename                       Stmts    Miss  Cover
---------------------------  -------  ------  --------
R/defaults.R                      -3      -1  -6.15%
R/generics.R                      +3       0  +0.99%
R/JointModel.R                    +3      -1  +1.01%
R/Link.R                         +37      +4  -7.27%
R/LinkComponent.R                +47      +5  +89.36%
R/LongitudinalGSF.R               +8       0  +100.00%
R/LongitudinalModel.R            +18     +12  -34.29%
R/LongitudinalRandomSlope.R       +7       0  +100.00%
TOTAL                           +120     +19  -0.17%

Results for commit: 835098c

Minimum allowed coverage is 80%

♻️ This comment has been updated with latest results

Copy link
Contributor

github-actions bot commented Feb 16, 2024

Unit Test Performance Difference

Test Suite $Status$ Time on main $±Time$ $±Tests$ $±Skipped$ $±Failures$ $±Errors$
Link 👶 $+0.90$ $+9$ $+1$ $0$ $0$
LinkComponent 👶 $+2.85$ $+18$ $+1$ $0$ $0$
LinkGSF 💀 $0.36$ $-0.36$ $-14$ $-1$ $0$ $0$
LinkNone 💀 $0.05$ $-0.05$ $-6$ $-1$ $0$ $0$
LinkRandomSlope 💀 $0.05$ $-0.05$ $-4$ $-1$ $0$ $0$
LongitudinalGSF 💚 $62.74$ $-3.03$ $0$ $0$ $0$ $0$
SurvivalExponential 💚 $28.58$ $-1.10$ $0$ $0$ $0$ $0$
brierScore 💚 $28.56$ $-1.41$ $0$ $0$ $0$ $0$
misc_models 💚 $22.86$ $-1.01$ $0$ $0$ $0$ $0$
stan_functions 💚 $49.81$ $-2.13$ $0$ $0$ $0$ $0$
Additional test case details
Test Suite $Status$ Time on main $±Time$ Test Case
Link 👶 $+0.01$ Link_prints_as_expected
Link 👶 $+0.43$ Link_works_as_expected
Link 👶 $+0.46$ link_none_works_as_expected
LinkComponent 👶 $+0.15$ LinkComponents_are_constructed_correctly_and_can_access_key_components
LinkComponent 👶 $+0.50$ Model_specific_links_return_the_correct_stan_code
LinkComponent 👶 $+0.57$ all_link_files_pass_stan_s_syntax_checker
LinkComponent 👶 $+1.62$ complete_models_with_links_pass_stan_s_syntax_checker
LinkComponent 👶 $+0.01$ print_works_as_expected
LinkGSF 💀 $0.02$ $-0.02$ LinkGSF_class_initialization_works_as_expected
LinkGSF 💀 $0.15$ $-0.15$ LinkGSF_returns_correct_defaults_when_no_arguments_are_supplied
LinkGSF 💀 $0.07$ $-0.07$ LinkGSF_user_constructor_works_as_expected_with_defaults
LinkGSF 💀 $0.08$ $-0.08$ Print_method_for_LinkGSF_works_as_expected
LinkGSF 💀 $0.01$ $-0.01$ link_gsf_dsld_user_constructor_works_as_expected_with_defaults
LinkGSF 💀 $0.01$ $-0.01$ link_gsf_identity_user_constructor_works_as_expected_with_defaults
LinkGSF 💀 $0.01$ $-0.01$ link_gsf_ttg_user_constructor_works_as_expected_with_defaults
LinkNone 💀 $0.03$ $-0.03$ LinkNone_object_is_generated_without_unexpected_input
LinkNone 💀 $0.02$ $-0.02$ Print_method_for_LinkNone_works_as_expected
LinkRandomSlope 💀 $0.03$ $-0.03$ LinkRandomSlope_smoke_tests
LinkRandomSlope 💀 $0.02$ $-0.02$ Print_method_for_LinkRandomSlope_works_as_expected
LongitudinalGSF 💚 $30.95$ $-1.22$ Can_recover_known_distributional_parameters_from_a_full_GSF_joint_model
LongitudinalGSF 💚 $31.18$ $-1.81$ Non_Centralised_parameterisation_compiles_without_issues
SurvivalExponential 💚 $25.12$ $-1.15$ SurvivalExponential_can_recover_true_parameter_no_covariates_
brierScore 💚 $28.43$ $-1.43$ brierScore_SurvivalQuantities_returns_same_results_as_survreg
misc_models 💚 $22.86$ $-1.01$ Longitudinal_Model_doesn_t_print_sampler_rejection_messages

Results for commit 30a7319

♻️ This comment has been updated with latest results.

Copy link
Contributor

github-actions bot commented Feb 19, 2024

Unit Tests Summary

  1 files   34 suites   5m 39s ⏱️
102 tests  78 ✅ 24 💤 0 ❌
783 runs  759 ✅ 24 💤 0 ❌

Results for commit 835098c.

♻️ This comment has been updated with latest results.

@gowerc
Copy link
Collaborator Author

gowerc commented Feb 20, 2024

@danielinteractive - Pending fixing up any CICD findings this is now complete and ready for review/merging

Copy link
Collaborator

@danielinteractive danielinteractive left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @gowerc , looks nice, see a few minor comments below

R/LongitudinalModel.R Show resolved Hide resolved
vignettes/extending-jmpost.Rmd Outdated Show resolved Hide resolved
vignettes/extending-jmpost.Rmd Show resolved Hide resolved
vignettes/extending-jmpost.Rmd Outdated Show resolved Hide resolved
vignettes/extending-jmpost.Rmd Outdated Show resolved Hide resolved
vignettes/extending-jmpost.Rmd Outdated Show resolved Hide resolved
vignettes/extending-jmpost.Rmd Outdated Show resolved Hide resolved
Co-authored-by: Daniel Sabanes Bove <[email protected]>
@gowerc gowerc merged commit ca04ed1 into main Feb 22, 2024
24 checks passed
@gowerc gowerc deleted the link-rework branch February 22, 2024 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rework Link functions
2 participants