Skip to content

Commit

Permalink
Merged main into feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Henrik Sparre Spiegelhauer (HSPU) committed Apr 8, 2024
2 parents 824de09 + 9d3d10f commit e9af83a
Show file tree
Hide file tree
Showing 17 changed files with 175 additions and 149 deletions.
13 changes: 7 additions & 6 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
always_allow_html: true
---

<!-- README.md is generated from README.Rmd. Please edit that file -->
# ramnog <a href="https://hta-pharma.github.io/ramnog/"><img src="man/figures/logo.png" align="right" height="138" alt="ramnog website" /></a>

# R packages for AMNOG analyses <a href="https://hta-pharma.github.io/ramnog/"><img src="man/figures/logo.png" align="right" height="138" alt="ramnog website" /></a>

Expand Down Expand Up @@ -38,14 +38,15 @@ dt |>

# Aim

The aim of the ramnog framework is that a programmer has to write minimal code, and no programming to in order to set-up a new AMNOG-type analyses. For each study, the programmer will need to make, adjust, or check the following four types of code:
The aim of {ramnog} is that a programmer has to write minimal code, and no programming to in order to set-up a new AMNOG-type analyses. For each study, the programmer will need to make, adjust, or check the following four types of code:

1. The definition of each endpoint (or group of endpoints).
2. A set of adam functions that makes any modifications to existing ADaM datasets (e.g., new age grouping in ADSL), or makes new ADaM datasets if none exist for the required output.
3. (If needed) Define a set of criteria for when an endpoint should be included in the results. A library of these criteria are stored in the companion package {chefCriteria}
4. A specification of the statistical functions used to summarize/analyze the data (usually found in the [chefStats](https://hta-pharma.github.io/chefStats/) package).
2. A set of ADaM functions that makes any modifications to existing ADaM datasets (e.g., new age grouping in ADSL), or makes new ADaM datasets if none exist for the required output.
3. (If needed) Define a set of criteria for when an endpoint should be included in the results. A library of these criteria are stored in the companion package {[chefCriteria](https://hta-pharma.github.io/chefCriteria/)}.
4. A specification of the statistical functions used to summarize/analyze the data. A library of these functions are provided in the {[chefStats](https://hta-pharma.github.io/chefStats/)} package.

A core principal of the frameworks design is __modularity__. The core functionality of the framework as specefied in the code in [chef](https://hta-pharma.github.io/chef/) should change slowly, while functionality that is subject to more frequent changes are sectioned off in other packages ([chefStats](https://hta-pharma.github.io/chefStats/) and [cheCriteria](https://hta-pharma.github.io/chefCriteria/))
A core principal of the frameworks design is __modularity__.
The core functionality of the framework resides in {[chef](https://hta-pharma.github.io/chef/)} and should change slowly, while functionality that is subject to more frequent changes are sectioned off in other packages ({[chefStats](https://hta-pharma.github.io/chefStats/)} and {[chefCriteria](https://hta-pharma.github.io/chefCriteria/)}).

# Contributing

Expand Down
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

<!-- README.md is generated from README.Rmd. Please edit that file -->
# ramnog <a href="https://hta-pharma.github.io/ramnog/"><img src="man/figures/logo.png" align="right" height="138" alt="ramnog website" /></a>

# R packages for AMNOG analyses <a href="https://hta-pharma.github.io/ramnog/"><img src="man/figures/logo.png" align="right" height="138" alt="ramnog website" /></a>

Expand Down Expand Up @@ -63,28 +63,31 @@ Wrapper package tying ecosystem together

# Aim

The aim of the ramnog framework is that a programmer has to write
minimal code, and no programming to in order to set-up a new AMNOG-type
analyses. For each study, the programmer will need to make, adjust, or
check the following four types of code:
The aim of {ramnog} is that a programmer has to write minimal code, and
no programming to in order to set-up a new AMNOG-type analyses. For each
study, the programmer will need to make, adjust, or check the following
four types of code:

1. The definition of each endpoint (or group of endpoints).
2. A set of adam functions that makes any modifications to existing
2. A set of ADaM functions that makes any modifications to existing
ADaM datasets (e.g., new age grouping in ADSL), or makes new ADaM
datasets if none exist for the required output.
3. (If needed) Define a set of criteria for when an endpoint should be
included in the results. A library of these criteria are stored in
the companion package {chefCriteria}
the companion package
{[chefCriteria](https://hta-pharma.github.io/chefCriteria/)}.
4. A specification of the statistical functions used to
summarize/analyze the data (usually found in the
[chefStats](https://hta-pharma.github.io/chefStats/) package).
summarize/analyze the data. A library of these functions are
provided in the
{[chefStats](https://hta-pharma.github.io/chefStats/)} package.

A core principal of the frameworks design is **modularity**. The core
functionality of the framework as specefied in the code in
[chef](https://hta-pharma.github.io/chef/) should change slowly, while
functionality that is subject to more frequent changes are sectioned off
in other packages ([chefStats](https://hta-pharma.github.io/chefStats/)
and [cheCriteria](https://hta-pharma.github.io/chefCriteria/))
functionality of the framework resides in
{[chef](https://hta-pharma.github.io/chef/)} and should change slowly,
while functionality that is subject to more frequent changes are
sectioned off in other packages
({[chefStats](https://hta-pharma.github.io/chefStats/)} and
{[chefCriteria](https://hta-pharma.github.io/chefCriteria/)}).

# Contributing

Expand Down
113 changes: 64 additions & 49 deletions tests/testthat/test-mild_ae_by_sex.R
Original file line number Diff line number Diff line change
@@ -1,55 +1,70 @@
test_that("Complex pipeline runs without errors",
{
# SETUP -------------------------------------------------------------------
testr::create_local_project()
mk_ep_def <- function() {
ep <- chef::mk_endpoint_str(
study_metadata = list(),
pop_var = "SAFFL",
pop_value = "Y",
treatment_var = "TRT01A",
treatment_refval = "Xanomeline High Dose",
stratify_by = list(c("SEX", "AGEGR1")),
data_prepare = mk_adae,
endpoint_label = "A",
custom_pop_filter = "TRT01A %in% c('Placebo', 'Xanomeline High Dose')",
group_by = list(list(AESOC = c(), AESEV=c())),
stat_by_strata_by_trt = list("N" = chefStats::n_subj,
"E" = chefStats::n_event),
stat_by_strata_across_trt = list("RR" = chefStats::RR,
"OR" = chefStats::OR),
stat_across_strata_across_trt = list("P-interaction" = chefStats::p_val_interaction)
)
}
test_that("Complex pipeline runs without errors", {
# SETUP -------------------------------------------------------------------
testr::create_local_project()
mk_ep_def <- function() {
ep <- chef::mk_endpoint_str(
study_metadata = list(),
pop_var = "SAFFL",
pop_value = "Y",
treatment_var = "TRT01A",
treatment_refval = "Xanomeline High Dose",
stratify_by = list(c("SEX", "AGEGR1")),
data_prepare = mk_adae,
endpoint_label = "A",
custom_pop_filter = "TRT01A %in% c('Placebo', 'Xanomeline High Dose')",
group_by = list(list(AESOC = c(), AESEV = c())),
stat_by_strata_by_trt = list(
"N" = chefStats::n_subj,
"E" = chefStats::n_event
),
stat_by_strata_across_trt = list(
"RR" = chefStats::RR,
"OR" = chefStats::OR
),
stat_across_strata_across_trt = list("P-interaction" = chefStats::p_val_interaction)
)
}



chef::use_chef(
pipeline_dir = "pipeline",
r_functions_dir = "R/",
pipeline_id = "01",
mk_endpoint_def_fn = mk_ep_def,
mk_adam_fn = list(mk_adae)
)
# ACT ---------------------------------------------------------------------
targets::tar_make()
chef::use_chef(
pipeline_dir = "pipeline",
r_functions_dir = "R/",
pipeline_id = "01",
mk_endpoint_def_fn = mk_ep_def,
mk_adam_fn = list(mk_adae)
)
# ACT ---------------------------------------------------------------------
targets::tar_make()

# EXPECT ------------------------------------------------------------------
x <- targets::tar_meta() |> data.table::as.data.table()
targets::tar_load(ep_stat)
expect_true(all(is.na(x$error)))
actual <- ep_stat[, .(
stat_filter,
endpoint_group_filter,
stat_result_label,
stat_result_description,
stat_result_qualifiers,
stat_result_value
)] |> setorder(endpoint_group_filter,
stat_filter,
stat_result_label,
stat_result_qualifiers)
# EXPECT ------------------------------------------------------------------
x <- targets::tar_meta() |> data.table::as.data.table()
targets::tar_load(ep_stat)
ep_stat <- ep_stat[order(c(
endpoint_id,
strata_var,
fn_type,
fn_name,
stat_filter,
stat_result_label,
stat_result_description
)), ]

expect_true(all(is.na(x$error)))
actual <- ep_stat[, .(
stat_filter,
endpoint_group_filter,
stat_result_label,
stat_result_description,
stat_result_qualifiers,
stat_result_value
)] |> setorder(
endpoint_group_filter,
stat_filter,
stat_result_label,
stat_result_qualifiers
)

expect_snapshot_value(x = as.data.frame(actual), tolerance = 1e-6, style = "json2")
})

expect_snapshot_value(x = as.data.frame(actual), tolerance = 1e-6, style = "json2")
})
30 changes: 13 additions & 17 deletions vignettes/debugging.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,39 @@ knitr::opts_chunk$set(
)
```

Debugging a chef pipeline is different than debugging normal R scripts or functions. This is because under the hood, chef makes heavy use of the {[targets](https://books.ropensci.org/targets/)} package.
Debugging a {chef} pipeline is different than debugging normal R scripts or functions. This is because under the hood, {chef} makes heavy use of the {[targets](https://books.ropensci.org/targets/)} package.

We present here two main approaches to debugging:

- Use the chef helper functions
- Use the {chef} helper functions
- Use the {[targets](https://books.ropensci.org/targets/)} helper functions

The chef debugging helper functions are best for normal debugging situations, while using the {targets} helper functions may be needed for more in-depth debugging sessions.
The {chef} debugging helper functions are best for normal debugging situations, while using the {targets} helper functions may be needed for more in-depth debugging sessions.


## Context - why debugging is different

In a chef pipeline, the execution environment is managed by {[targets](https://books.ropensci.org/targets/)}, and does **not** take place in your working environment. As a side-effect, it is not be as straightforward to reproduce errors or trace them back to their source. Specifically:
In a {chef} pipeline, the execution environment is managed by {[targets](https://books.ropensci.org/targets/)}, and does **not** take place in your working environment. As a side-effect, it is not be as straightforward to reproduce errors or trace them back to their source. Specifically:

- Each target in a {targets} pipeline is run in an new R session that closes upon completion or hitting an error. This means that the working environment is clean for every run, which is beneficial for reproducibility but can make it harder to debug because the state that led to an error might not be readily accessible.

- Targets uses a caching mechanism to avoid re-running successful tasks. While this feature enhances efficiency, it can make debugging tricky. Understanding whether a bug is due to current code or cached results might not be straightforward
- {targets} uses a caching mechanism to avoid re-running successful tasks. While this feature enhances efficiency, it can make debugging tricky. Understanding whether a bug is due to current code or cached results might not be straightforward

- Sometimes, the error messages from a targets pipeline can be cryptic or not very informative, making it harder to figure out what's going wrong.
- Sometimes, the error messages from a {targets} pipeline can be cryptic or not very informative, making it harder to figure out what's going wrong.

- Debugging often requires interactively running code to inspect objects and their states. However, {targets} is designed for non-interactive batch (and sometimes parallel) execution, which can make interactive debugging less straightforward. For example, you cannot just insert a `browser()` into the function that errored out like you would in interactive debugging.

## Chef style debugging
## {chef} style debugging

The most common errors will be due to errors stemming from improper user inputs (e.g. the user-supplied functions that that generate the input ADaM datasets, or contain the statistical methods). To debug these, it is easiest if the programmer has access to the state of the program at the time it errored-out.


Errors stemming from user function are split into two types:

- Firstly, those relating to the function formals or more specifically whether the function inputs will match with those supplied by {chef}.

- Secondly, errors which arise during the evaluation of the function and validation of its output. These errors can come from bug in the functions causing crashing at runtime, or functions which return an invalid output.

### Input errors.
### Input errors

Input errors stems from mismatch between the expected arguments of a function and those arguments which {chef} supplies. Errors in this domain could stem from improperly defined statistical functions or for instance statistical functions applied to wrong statistical types in the endpoint specification.

Expand All @@ -73,7 +72,7 @@ Expected arguments: (required, [optional])
specific_arg, study_metadata []
```

In the above example, the expect-but-no-supplied argument `specific_arg` is clearly described as well as those arguments chef supplies (`study_metadata`) and the full function argument specification.
In the above example, the expect-but-no-supplied argument `specific_arg` is clearly described as well as those arguments {chef} supplies (`study_metadata`) and the full function argument specification.

---

Expand All @@ -99,11 +98,11 @@ Either state all supplied args explicitely or use dots (...) as a passthrough (r

In the under-defined example the function does not expect the supplied `study_metadata` argument. This example also shows how optional arguments (with default values) are displayed.

### Evaluation and validation errors.
### Evaluation and validation errors

Chef has built-in helpers that provide the user the state of the pipeline when a user-supplied function errors-out. By using these helpers, the programmer can access the workspace (i.e., all objects and functions) at the point of the error. Then they can debug interactively like normal R debugging.
{chef} has built-in helpers that provide the user the state of the pipeline when a user-supplied function errors-out. By using these helpers, the programmer can access the workspace (i.e., all objects and functions) at the point of the error. Then they can debug interactively like normal R debugging.

In broad terms Chef will supply the user with the function and input parameters that lead to an erroneous evaluation. Chef will supply debugging sessions if the function crashes or if the function output is not compliant.([Statistical output](methods_stat.html#output-specification-shared-for-all-types), [Criterion output](methods_criteria.html#output-specifications))
In broad terms {chef} will supply the user with the function and input parameters that lead to an erroneous evaluation. {chef} will supply debugging sessions if the function crashes or if the function output is not compliant. ([Statistical output](methods_stat.html#output-specification-shared-for-all-types), [Criterion output](methods_criteria.html#output-specifications))

For failures during the evaluation the error message will contain the keyword `EVALUATE` while non-conforming outputs will result in errors with the `VALIDATE` keyword. Sample error messages can be seen in the following.

Expand All @@ -114,7 +113,7 @@ Error during evaluation of: log(1)
Failed to VALIDATE function output with error:
Expected (data.table::data.table) Found: numeric
# Error message wrt. evaluation.
# Error message wrt. evaluation
Error during evaluation of: P-interaction
Failed to EVALUATE function with error:
Expand Down Expand Up @@ -192,9 +191,6 @@ We can then play around with the function, given the supplied inputs.
In this toy example we simply have a function which throws an error - a simple error to fix. However, this tool provides you with the possiblity to explore and tinker with your functions in the context which they fail in the pipeline.
NB. as mentioned in the console message - remember to update you changes to the source code!




## Targets style debugging

There are several approaches to debugging a pipeline that is erroring out. Find much more details in the [targets manual](https://books.ropensci.org/targets/debugging.html):
Expand Down
4 changes: 2 additions & 2 deletions vignettes/dev_git.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ Specifically each package always has the `main` branch that is functioning and d

# Definition of done {#dod}

A feature is defined as done and ready for a PR into main only once it has been properly tested. Testing consists of:
A feature branch is defined as done and ready for a PR (pull request) into main branch only once it has been properly tested. Testing consists of:

1. Unit-tests to test the basic functionality
2. Integration testing to ensure all features work together
3. Passing the CI/CD

Unit-testing and ingegration testing are done using the [testthat](https://testthat.r-lib.org/) framework. New features usually, but no always require new tests.

If you are developing, for example a new statistical function for chefStats then it must be checked with unit tests.
If you are developing, for example a new statistical function for {chefStats} then it must be checked with unit tests.

Additionally, all previous unit-tests and integration tests need to pass. You can check this by running `devtools::test()` in the respective packages.

Expand Down
9 changes: 6 additions & 3 deletions vignettes/ep_overview.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ dt <- data.table::data.table(
)
)
dt |>
knitr::kable() |>
knitr::kable(dt, booktabs = TRUE, escape = FALSE, format = "html",
col.names = c("Type", kableExtra::linebreak("Section"), "Argument name")) |>
kableExtra::kable_styling(full_width = FALSE) |>
kableExtra::column_spec(1, width = "15em") |>
kableExtra::column_spec(2, width = "30em") |>
kableExtra::column_spec(3, width = "15em") |>
kableExtra::collapse_rows(columns = 1:2)
```
5 changes: 3 additions & 2 deletions vignettes/ep_spec_adam_data.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ The ADaM functions, once defined, need to be linked to the corresponding endpoin
```{r, eval=FALSE}
# Example of endpoint specification of ADaM function.
# The dots must be replaced with other required parameters.
ep_spec_ex1_3 <- chef::mk_endpoint_str(data_prepare = mk_adam_ex1_2,
...)
ep_spec_ex1_3 <- chef::mk_endpoint_str(
data_prepare = mk_adam_ex1_2,
...)
```
Loading

0 comments on commit e9af83a

Please sign in to comment.