05. Survival Analysis

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