ltRISK

Overview

ltRISK estimates lifetime and age-conditional risks of developing or dying from cancer using population-based cancer registry data. It provides a reproducible R interface for comparing several established approaches used in cancer surveillance:

The main wrapper, calc_ltr(), returns risk estimates and confidence intervals. Both gamma-based and delta-method confidence intervals are supported through the ci_method argument. DevCan, Wun, AMP, and cumulative risk support the piecewise mid-age group joinpoint (PMAJ) model for age-specific rates. Wun retains its historical piecewise-constant calculation by default; set maj_method = "pmaj" explicitly to use PMAJ.

Installation

You can install ltRISK from R-universe with:

install.packages(
  "ltRISK",
  repos = c("https://gigu003.r-universe.dev", "https://cloud.r-project.org")
)

You can install the development version from GitHub with:

# install.packages("devtools")
devtools::install_github("gigu003/ltRISK")

Quick example: DevCan method

The package includes example data from DevCan-related publications and SEER data. The following example estimates the lifetime and age-conditional risks of developing breast cancer using the DevCan method.

library(ltRISK)

data("seer_example_data")

breast <- seer_example_data[seer_example_data$site == "Breast", ]
all_sites <- seer_example_data[
  seer_example_data$site == "All" & seer_example_data$sex == 0,
]

calc_ltr(
  ages = breast$ages,
  cancer = breast$cancer,
  cancer_death = breast$cancer_death,
  death = breast$death,
  pys = breast$pys,
  risk_func = "devcan",
  maj_method = "constant",
  ci_method = "gamma",
  age_start = c(0, 30, 50, 70),
  age_end = c(30, 50, 70, Inf),
  digits = 4
)
#>    start end    risk   lower   upper
#> 1      0  30  0.0470  0.0424  0.0519
#> 2      0  50  1.8995  1.8708  1.9286
#> 3      0  70  7.7861  7.7130  7.8598
#> 4      0 Inf 13.3198 13.2170 13.4235
#> 5     30  50  1.8817  1.8529  1.9108
#> 6     30  70  7.8609  7.7868  7.9355
#> 7     30 Inf 13.4816 13.3773 13.5868
#> 8     50  70  6.2505  6.1793  6.3224
#> 9     50 Inf 12.1264 12.0217 12.2320
#> 10    70 Inf  7.3149  7.2202  7.4109

The same calculation can be written with the data-frame interface calc_ltr_df(), which is convenient for pipe-based workflows:

calc_ltr_df(
  breast,
  ages = ages,
  cancer = cancer,
  cancer_death = cancer_death,
  death = death,
  pys = pys,
  risk_func = "devcan",
  maj_method = "constant",
  ci_method = "gamma",
  age_start = c(0, 30, 50, 70),
  age_end = c(30, 50, 70, Inf),
  digits = 4
)
#>    start end    risk   lower   upper
#> 1      0  30  0.0470  0.0424  0.0519
#> 2      0  50  1.8995  1.8708  1.9286
#> 3      0  70  7.7861  7.7130  7.8598
#> 4      0 Inf 13.3198 13.2170 13.4235
#> 5     30  50  1.8817  1.8529  1.9108
#> 6     30  70  7.8609  7.7868  7.9355
#> 7     30 Inf 13.4816 13.3773 13.5868
#> 8     50  70  6.2505  6.1793  6.3224
#> 9     50 Inf 12.1264 12.0217 12.2320
#> 10    70 Inf  7.3149  7.2202  7.4109

For multiple registry strata, calc_ltr_df() can calculate risks by group:

calc_ltr_df(
  seer_example_data,
  by = c("site", "sex"),
  ages = ages,
  cancer = cancer,
  cancer_death = cancer_death,
  death = death,
  pys = pys,
  risk_func = "devcan",
  maj_method = "constant",
  ci_method = "delta",
  age_start = 0,
  age_end = Inf,
  digits = 4
)
#>     site sex start end    risk   lower   upper
#> 1    All   0     0 Inf 39.1670 39.1072 39.2268
#> 2    All   1     0 Inf 39.4881 39.4018 39.5744
#> 3    All   2     0 Inf 38.9734 38.8899 39.0569
#> 4 Breast   2     0 Inf 13.3198 13.2168 13.4228

Age-range behavior

By default, calc_ltr() returns every valid combination of age_start and age_end. Use age_combine = "pairwise" for positional, mutually exclusive ranges such as 0–40, 40–50, and 50 years to the end of life.

calc_ltr(
  ages = all_sites$ages,
  cancer = all_sites$cancer,
  cancer_death = all_sites$cancer_death,
  death = all_sites$death,
  pys = all_sites$pys,
  age_start = c(0, 40, 50),
  age_end = c(40, 50, Inf),
  age_combine = "pairwise",
  ci_method = "delta",
  digits = 4
)
#>   start end    risk   lower   upper
#> 1     0  40  2.0321  2.0212  2.0431
#> 2    40  50  2.8292  2.8155  2.8429
#> 3    50 Inf 38.3877 38.3238 38.4515

Faster large analyses

For point estimates only, use ci_method = "none". DevCan, AMP, Wun, and cumulative-risk models use analytic variances by default where available; variance_method = "finite_difference" remains available for verification. For many independent registry strata, calc_ltr_df() supports cross-platform parallel processing.

risks <- calc_ltr_df(
  seer_example_data,
  by = c("site", "sex"),
  ci_method = "delta",
  variance_method = "analytic",
  parallel = TRUE,
  workers = 2,
  return_variance = TRUE
)

Repeated calculations use internal memoisation. Long-running pipelines can inspect or clear these caches explicitly:

ltr_cache_info()
clear_ltr_cache()

For a large grouped job whose inputs are unlikely to repeat, use cache = "none" in calc_ltr_df(). This discards memoised entries after each group, including on reused PSOCK workers, and reduces peak cache growth.

Package developers can reproduce the confidence-interval timing comparison from the source tree with:

Rscript inst/benchmarks/benchmark-ci.R

The DevCan implementation is externally regression-tested against the female breast cancer estimates and confidence intervals in Fay et al. (2003), Table II. A seeded parametric coverage diagnostic can be run from the source tree:

Rscript inst/validation/simulate-ci-coverage.R 1000

The variance model treats incidence, cancer-death, and other-death counts as independent Poisson components and treats person-years as fixed. Comparisons of overlapping populations or periods require covariance information that the package does not estimate automatically.

Formatting and comparisons

format_risk_ci() combines estimates and confidence limits for tables while retaining the precision selected in calc_ltr(). With return_variance = TRUE, grouped results can also be passed to ztest(), pairwise_ztest(), or trend_test().

sex_risks <- calc_ltr_df(
  seer_example_data[
    seer_example_data$site == "All" &
      seer_example_data$sex %in% c(1, 2),
  ],
  by = "sex",
  ci_method = "delta",
  return_variance = TRUE,
  digits = 4
)

format_risk_ci(sex_risks)
#>   sex start end    risk   lower   upper    variance         se
#> 1   1     0 Inf 39.2052 39.1189 39.2915 0.001938403 0.04402730
#> 2   2     0 Inf 38.7289 38.6453 38.8124 0.001816086 0.04261556
#>                    risk_95ci
#> 1 39.2052 (39.1189, 39.2915)
#> 2 38.7289 (38.6453, 38.8124)
ztest(sex_risks, group = "sex", ref = 2, compare = 1)
#>   group_compare group_ref start end risk_compare risk_ref difference       se
#> 1             1         2     0 Inf      39.2052  38.7289     0.4763 0.061274
#>          z            p    lower    upper
#> 1 7.773295 7.647009e-15 0.356205 0.596395

The same estimates can be calculated with delta-method confidence intervals:

calc_ltr(
  ages = breast$ages,
  cancer = breast$cancer,
  cancer_death = breast$cancer_death,
  death = breast$death,
  pys = breast$pys,
  risk_func = "devcan",
  maj_method = "constant",
  ci_method = "delta",
  age_start = c(0, 30, 50, 70),
  age_end = c(30, 50, 70, Inf),
  digits = 4
)
#>    start end    risk   lower   upper
#> 1      0  30  0.0470  0.0423  0.0517
#> 2      0  50  1.8995  1.8707  1.9284
#> 3      0  70  7.7861  7.7128  7.8594
#> 4      0 Inf 13.3198 13.2168 13.4228
#> 5     30  50  1.8817  1.8527  1.9106
#> 6     30  70  7.8609  7.7866  7.9351
#> 7     30 Inf 13.4816 13.3771 13.5861
#> 8     50  70  6.2505  6.1791  6.3220
#> 9     50 Inf 12.1264 12.0214 12.2313
#> 10    70 Inf  7.3149  7.2199  7.4100

To estimate the risk of dying from cancer instead of developing cancer, set type = "dying".

calc_ltr(
  ages = breast$ages,
  cancer = breast$cancer,
  cancer_death = breast$cancer_death,
  death = breast$death,
  pys = breast$pys,
  risk_func = "devcan",
  maj_method = "constant",
  ci_method = "gamma",
  type = "dying",
  age_start = c(0, 30, 50, 70),
  age_end = c(30, 50, 70, Inf),
  digits = 4
)
#>    start end   risk  lower  upper
#> 1      0  30 0.0051 0.0037 0.0069
#> 2      0  50 0.2901 0.2789 0.3016
#> 3      0  70 1.4657 1.4339 1.4981
#> 4      0 Inf 3.2027 3.1514 3.2548
#> 5     30  50 0.2893 0.2781 0.3009
#> 6     30  70 1.4830 1.4507 1.5158
#> 7     30 Inf 3.2465 3.1944 3.2994
#> 8     50  70 1.2276 1.1965 1.2593
#> 9     50 Inf 3.0413 2.9890 3.0945
#> 10    70 Inf 2.1361 2.0862 2.1870

Other supported methods

The same calc_ltr() interface can be used with the Wun and AMP methods by changing risk_func.

# Wun method
calc_ltr(
  ages = all_sites$ages,
  cancer = all_sites$cancer,
  cancer_death = all_sites$cancer_death,
  death = all_sites$death,
  pys = all_sites$pys,
  risk_func = "wun",
  maj_method = "pmaj",
  pmaj_sub_interval = 1,
  ci_method = "gamma",
  age_start = c(0, 50, 70),
  age_end = Inf,
  digits = 4
)
#>   start end    risk   lower   upper
#> 1     0 Inf 38.7774 38.7184 38.8364
#> 2    50 Inf 38.2360 38.1731 38.2990
#> 3    70 Inf 30.2010 30.1274 30.2748

# AMP method
calc_ltr(
  ages = all_sites$ages,
  cancer = all_sites$cancer,
  cancer_death = all_sites$cancer_death,
  death = all_sites$death,
  pys = all_sites$pys,
  risk_func = "amp",
  ci_method = "gamma",
  age_start = c(0, 50, 70),
  age_end = Inf,
  digits = 4
)
#>   start end    risk   lower   upper
#> 1     0 Inf 33.2386 33.1989 33.2784
#> 2    50 Inf 32.1052 32.0634 32.1470
#> 3    70 Inf 23.3479 23.3008 23.3951

Traditional cumulative risk

Traditional cumulative risk can be calculated directly with calc_ltr() by setting risk_func = "cumulative", or with the lower-level cumulative() and get_risk() workflow. Unlike DevCan, Wun, or AMP, this approach does not account for competing mortality.

calc_ltr(
  ages = all_sites$ages,
  cancer = all_sites$cancer,
  cancer_death = all_sites$cancer_death,
  death = all_sites$death,
  pys = all_sites$pys,
  risk_func = "cumulative",
  type = "developing",
  maj_method = "constant",
  ci_method = "delta",
  age_start = 0,
  age_end = c(75, 85),
  digits = 4
)
#>   start end    risk   lower   upper
#> 1     0  75 31.9419 31.9007 31.9831
#> 2     0  85 42.4122 42.3572 42.4673

cumu_developing <- cumulative(
  ages = all_sites$ages,
  cancer = all_sites$cancer,
  cancer_death = all_sites$cancer_death,
  death = all_sites$death,
  pys = all_sites$pys,
  type = "developing",
  maj_method = "constant"
)

get_risk(cumu_developing, age_start = 0, age_end = c(75, 85))
#> [1] 0.3194190 0.4241222

Learn more

For a detailed introduction to the implemented methods, confidence interval calculations, and additional examples, see the package vignette:

vignette("use-ltRISK", package = "ltRISK")