Overview
See the Glossary for term definitions used throughout this project.
- Survival analysis of the CoMMpass cohort stratified by clinical and cytogenetic features
- Kaplan-Meier curves with log-rank tests for group comparisons
- Cox proportional hazards models for multivariate analysis
- Forest plots of hazard ratios with 95% confidence intervals
Note: This vignette was built in CI with sample_limit=20. Local builds default to 200 samples. Numbers below reflect the CI subset.
Overall Survival
Kaplan-Meier estimate of the overall survival function for the full patient cohort.

Median overall survival has not been reached in this cohort (n = 994 patients). This indicates that more than 50% of patients remain alive at last follow-up, which is consistent with improving outcomes in multiple myeloma with modern therapies.
Survival by ISS Stage
- ISS stratifies by serum albumin and beta-2 microglobulin
- ISS III is expected to have worse survival than ISS I
- See the EDA vignette for ISS stage distributions

Survival by Cytogenetic Risk Group
Cytogenetic risk per IMWG 2014 criteria: high-risk = t(4;14) OR t(14;16) OR del(17p); standard-risk = all others.
Data not available: Target
vig_km_risk returned NULL. See
Data Sources.
Survival by Individual Cytogenetic Markers
Each marker is analyzed separately: patients with vs without the alteration.
Data not available: Target
vig_km_markers returned NULL. See
Data Sources.
Cox Proportional Hazards
Basic Model: Age + Gender
Hazard ratios for age and gender from a minimal Cox PH model.
Generating code
{
if (is.null(cox_basic) || is.null(cox_basic$hazard_ratios))
return(NULL)
hr <- cox_basic$hazard_ratios
for (col in names(hr)) {
if (is.numeric(hr[[col]])) {
hr[[col]] <- if (col == "p.value")
signif(hr[[col]], 4)
else round(hr[[col]], 3)
}
}
caption <- paste0("Cox PH model: age + gender. ", "term = covariate name. ",
"HR (hazard ratio) > 1 = increased hazard (worse survival). ",
"CI = 95% confidence interval for HR. ", "p.value = Wald test significance. ",
"n = ", cox_basic$n, " patients, ", cox_basic$n_events,
" events. C-index = ", round(cox_basic$concordance, 3),
". ", "* p < 0.05, ** p < 0.01, *** p < 0.001.")
DT::datatable(hr, rownames = FALSE, filter = "top", options = list(pageLength = 15,
scrollX = TRUE), caption = htmltools::tags$caption(style = "caption-side: top; text-align: left;",
caption))
}
Full Model: Age + Gender + ISS + Cytogenetic Risk
Multivariable Cox model adjusting for ISS stage and IMWG cytogenetic risk classification.
Generating code
{
if (is.null(cox_full) || is.null(cox_full$hazard_ratios))
return(NULL)
hr <- cox_full$hazard_ratios
for (col in names(hr)) {
if (is.numeric(hr[[col]])) {
hr[[col]] <- if (col == "p.value")
signif(hr[[col]], 4)
else round(hr[[col]], 3)
}
}
caption <- paste0("Multivariate Cox PH model with clinical and cytogenetic covariates. ",
"term = covariate name. ", "HR > 1 = increased hazard (worse survival). ",
"CI = 95% confidence interval. ", "n = ", cox_full$n,
" patients, ", cox_full$n_events, " events. ", "C-index = ",
round(cox_full$concordance, 3), ". ", "Covariates: ",
paste(cox_full$covariates_used, collapse = ", "), ".")
DT::datatable(hr, rownames = FALSE, filter = "top", options = list(pageLength = 15,
scrollX = TRUE), caption = htmltools::tags$caption(style = "caption-side: top; text-align: left;",
caption))
}
Forest Plot
Forest plot showing hazard ratios from the full multivariate Cox regression. Each row represents a covariate; the point estimate is the HR with 95% CI bars. HR > 1 indicates increased risk; HR < 1 indicates a protective effect. The dashed vertical line marks HR = 1 (no effect).

Proportional Hazards Assumption
Schoenfeld residuals test for the proportional hazards assumption. A significant p-value (< 0.05) suggests the covariate’s effect changes over time, violating the PH assumption. Covariates that fail this test may need time-varying coefficients or stratification.
Generating code
{
cox_for_ph <- if (!is.null(cox_full))
cox_full
else cox_basic
if (is.null(cox_for_ph) || is.null(cox_for_ph$ph_test))
return(NULL)
ph <- cox_for_ph$ph_test
ph_table <- as.data.frame(ph$table)
ph_table$variable <- rownames(ph_table)
ph_table <- ph_table[, c("variable", "chisq", "p")]
names(ph_table) <- c("Variable", "Chi-squared", "p-value")
ph_table$`Chi-squared` <- round(ph_table$`Chi-squared`, 2)
ph_table$`p-value` <- signif(ph_table$`p-value`, 4)
caption <- paste0("Proportional hazards assumption test (cox.zph). ",
"Variable = covariate tested. ", "Chi-squared = Schoenfeld residual test statistic. ",
"p-value < 0.05 indicates the PH assumption may be violated ",
"(hazard ratio changes over time). ", "If violated, time-varying coefficients or stratification ",
"should be considered.")
DT::datatable(ph_table, rownames = FALSE, filter = "top",
options = list(pageLength = 15, scrollX = TRUE), caption = htmltools::tags$caption(style = "caption-side: top; text-align: left;",
caption))
}
Model Comparison
Comparison of nested Cox models using likelihood ratio test, AIC, and concordance index. The full model includes ISS stage and cytogenetic risk in addition to age and gender. A significant likelihood ratio test indicates the additional covariates improve model fit.
Generating code
{
if (is.null(cox_basic) || is.null(cox_full))
return(NULL)
if (is.null(cox_basic$concordance) || is.null(cox_full$concordance))
return(NULL)
comp <- data.frame(Model = c("Basic (age + gender)", paste0("Full (",
paste(cox_full$covariates_used, collapse = " + "), ")")),
N = c(cox_basic$n, cox_full$n), Events = c(cox_basic$n_events,
cox_full$n_events), C_index = c(round(cox_basic$concordance,
3), round(cox_full$concordance, 3)), stringsAsFactors = FALSE)
caption <- paste0("Cox model comparison. ", "Model = covariates included. ",
"N = patients with complete data for all covariates. ",
"Events = observed deaths. ", "C-index = concordance statistic ",
"(0.5 = random, 1.0 = perfect discrimination). ", "Higher C-index indicates better prognostic discrimination.")
DT::datatable(comp, rownames = FALSE, filter = "top", options = list(pageLength = 10,
scrollX = TRUE), colnames = c("Model", "N", "Events",
"C-index"), caption = htmltools::tags$caption(style = "caption-side: top; text-align: left;",
caption))
}
Survival by Gene Expression
Patients are split at the median VST expression of each top DE gene into “High” and “Low” groups. This connects differential expression results to clinical outcomes.
Note on multiple testing: With 5 genes tested, a Bonferroni-corrected significance threshold is p < 0.01.
Expression by Cytogenetic Subtype
Violin/box plots showing gene expression (VST) stratified by cytogenetic marker status. This reveals whether specific alterations drive expression changes in top DE genes.
Show code
plots <- safe_tar_read("vig_expr_by_subtype")
if (is.list(plots) && !inherits(plots, c("gtable", "grob"))) {
for (p in plots) {
if (inherits(p, c("gtable", "grob", "gTree"))) {
grid::grid.newpage(); grid::grid.draw(p)
} else print(p)
}
}
Next Steps
- Time-varying coefficients: For covariates violating the PH assumption.
- Cure models: If a plateau is observed in KM curves, consider mixture cure models.
- See the cytogenetic landscape for alteration frequencies underlying risk groups.
- See the DE results for gene signatures that could inform survival stratification.
Data Sources
Results in this vignette are derived from the MMRF CoMMpass study (MMRF-COMMPASS, ~1,143 patients), downloaded via TCGAbiolinks. The pipeline runs with a configurable sample_limit (default 200; CI uses 20).
For full citations, data access tiers, and the distinction between pipeline data and synthetic test data, see the Data Sources vignette.
Recent Changes
Recent project commits with lines added, files changed, and change categories.
Bayesian Survival Models
Bayesian Cox PH models fit with brms using weakly informative priors. These complement the frequentist models above by providing posterior distributions, credible intervals, and natural hierarchical structure (e.g., ISS-stage random intercepts).
Note
Bayesian models use cue = "never" — they only run when explicitly requested via tar_make(names = bayes_cox_basic). MCMC compilation takes several minutes.
Frequentist vs Bayesian Comparison
Reproducibility
Session Info (click to expand)
Show code
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] targets_1.12.0
#>
#> loaded via a namespace (and not attached):
#> [1] base64url_1.4 gtable_0.3.6 jsonlite_2.0.0 dplyr_1.2.1
#> [5] compiler_4.6.1 tidyselect_1.2.1 jquerylib_0.1.4 callr_3.8.0
#> [9] scales_1.4.0 yaml_2.3.12 fastmap_1.2.0 ggplot2_4.0.3
#> [13] R6_2.6.1 generics_0.1.4 igraph_2.3.3 knitr_1.51
#> [17] htmlwidgets_1.6.4 backports_1.5.1 tibble_3.3.1 maketools_1.3.2
#> [21] bslib_0.11.0 pillar_1.11.1 RColorBrewer_1.1-3 rlang_1.3.0
#> [25] DT_0.34.0 cachem_1.1.0 xfun_0.60 sass_0.4.10
#> [29] sys_3.4.3 S7_0.2.2 otel_0.2.0 cli_3.6.6
#> [33] withr_3.0.3 magrittr_2.0.5 crosstalk_1.2.2 ps_1.9.3
#> [37] digest_0.6.39 grid_4.6.1 processx_3.9.0 secretbase_1.3.0
#> [41] lifecycle_1.0.5 prettyunits_1.2.0 vctrs_0.7.3 evaluate_1.0.5
#> [45] glue_1.8.1 data.table_1.18.4 farver_2.1.2 codetools_0.2-20
#> [49] buildtools_1.0.0 rmarkdown_2.31 tools_4.6.1 pkgconfig_2.0.3
#> [53] htmltools_0.5.9