8. Portability of the R API to a non-PDAC cohort

Goal

This package is PDAC-specific in scope. The bundled atlas, the anchor reference set, and every validated claim in the other vignettes are PDAC. Nothing here extends that scope.

What this vignette does establish is narrower and purely technical: the R API does not hard-code PDAC. The 12-template trajectory catalog, the 3-axis + 2-gate audit score, and the Evidence Math layer operate on a per-stage expression model (Normal -> Early -> Mid -> Late), so project_user_cohort() accepts a cohort from another disease without modification. Whether the results mean anything there is an open question this package does not answer, and the “Limitations” section below says so explicitly.

Read this as documentation of an interface boundary – useful if you want to reuse the machinery on your own data – and not as a claim that pdactrace generalises.

We use a synthetic CRC-flavored cohort (50 genes x 24 samples, balanced across stages and cohorts) so the vignette renders without external dataset downloads. The exact same call signature works on a real CRC cohort – e.g. TCGA-COAD, CPTAC-COAD, or GSE39582 – as long as you supply a stage column with values in c("Normal", "Early", "Mid", "Late") (Appendix A in vignette("user_cohort_extension") maps AJCC and TNM to these four levels).

Step 1 – Build a synthetic non-PDAC cohort

# 50 genes x 24 samples; 6 per stage; 2 synthetic cohorts.
n_genes  <- 50
n_per    <- 6
stages   <- factor(rep(c("Normal", "Early", "Mid", "Late"),
                       each = n_per),
                   levels = c("Normal", "Early", "Mid", "Late"))
cohorts  <- rep(c("CohortA", "CohortB"), length.out = length(stages))

# Plant a CRC-specific Early-onset signal in the first 8 genes:
# log-fold change increases monotonically from Normal to Early
# with a small additional bump at Mid. This is the kind of
# signal pdactrace's `Early_Burst_Up` template should pick up.
counts <- matrix(rnbinom(n_genes * length(stages),
                         size = 5, mu = 50),
                 nrow = n_genes, ncol = length(stages),
                 dimnames = list(paste0("CRC_GENE_", seq_len(n_genes)),
                                 paste0("S", seq_along(stages))))

# Inject Early-onset signal into 8 genes:
multiplier <- ifelse(stages == "Normal", 1,
                     ifelse(stages == "Early", 4,
                            ifelse(stages == "Mid", 4.5, 4.5)))
for (g in 1:8) counts[g, ] <- as.integer(counts[g, ] * multiplier)

coldata <- data.frame(
  sample = colnames(counts),
  stage  = as.character(stages),
  cohort = cohorts,
  stringsAsFactors = FALSE)
table(coldata$stage)
#> 
#>  Early   Late    Mid Normal 
#>      6      6      6      6

Step 2 – Project through the framework (one call)

project_user_cohort() does not care that the cohort is CRC and not PDAC. The function takes the stage-aware counts, fits DESeq2 LRT, classifies the per-gene profile against the 12-template catalog, assembles user evidence, and runs the audit score.

res_crc <- project_user_cohort(
  rna        = counts,
  coldata    = coldata,
  stage_col  = "stage",
  cohort_col = "cohort")

res_crc
#> <pdactrace_user_projection>
#>   Input:   50 genes; protein layer: no
#>   Audit:   50 genes scored
#>   Classes: high=2 supp_unc=4 penalized=0 excluded=0 low=44

# The 8 planted Early-onset genes should dominate the high-rank list:
top_crc <- res_crc$audit[order(-audit_score)][1:10]
top_crc[, .(gene_symbol, audit_class, rna_pattern, audit_score)]

The exact same call works on a real CRC cohort (e.g. TCGA-COAD counts + clinical stage table mapped via the AJCC harmonisation in Appendix A of vignette("user_cohort_extension")).

Step 3 – What changes between PDAC and non-PDAC?

Layer What is cancer-specific What stays the same
Trajectory catalog (12 templates) nothing – shapes are biology-agnostic applies unchanged
3-axis + 2-gate audit rule nothing – operates on user evidence applies unchanged
External anchor set PDAC-specific (30 anchors curated for PDAC serum biomarkers) not portable; users projecting their own cancer should curate their own anchor set
Translation class A / B / C uses the bundled atlas’s RNA direction as the tissue reference re-derive from the user’s own RNA pattern when projecting non-PDAC
Bundled atlas (pdactrace_reference) PDAC-only (10,113 genes from PDAC cohorts) replace with user’s projection for non-PDAC analysis

The framework’s three “always-portable” components are:

  1. The 12-template trajectory match (classify_trajectory()).
  2. The audit-score rule (compute_audit_score() over user evidence).
  3. The Evidence Math layer (evidence_math() / compare_genes()).

The two “swap-out” components are:

  1. The reference atlas – replaced by user’s projection.
  2. The anchor evaluation set – replaced by user’s curated positive list (passed via anchors = in evaluate_anchor_enrichment()).

Step 4 – Translation discipline transfers, but the join target changes

For PDAC, project_user_serum_cohort()’s link_to_atlas = TRUE joins your serum data against pdactrace_reference’s RNA direction to assign Class A / B / C. For a non-PDAC cohort, the correct reference is your own RNA projection, not pdactrace’s PDAC atlas. Pass link_to_atlas = FALSE and assemble the class yourself by joining on the user RNA pattern:

# Fictional CRC serum cohort (PDAC -> COL: 'CRC' label, 'HC')
serum_summary <- project_user_serum_cohort(
  intensity     = my_crc_serum_intensity,
  coldata       = my_crc_serum_coldata,
  pdac_label    = "CRC",   # use your cancer's group label
  hc_label      = "HC",
  link_to_atlas = FALSE)   # do NOT join pdactrace's PDAC atlas

# Then assemble class yourself:
# Class A if user_rna_dir == sign(serum_log2fc_PDAC_vs_HC), B if
# opposite, C otherwise.

Limitations

This vignette uses a synthetic cohort to demonstrate the call signature. For a real cross-cancer publication study you would:

  • Use a real cohort (TCGA-COAD has 461 samples with stage labels; GSE39582 has 566 with TNM).
  • Curate your own positive anchor set for the cancer of interest.
  • Validate with an independent held-out cohort.
  • Acknowledge the limitations of the 12-template catalog for cancers with markedly different progression biology (e.g. hematologic malignancies where stage maps differently).

The demonstration here establishes that the framework’s R API accepts non-PDAC input, not that the bundled PDAC atlas’s results generalize to other cancers (they should not – that would be a different study, with its own cohorts and its own anchor set).

sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 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] data.table_1.18.6.1 pdactrace_0.99.25   rmarkdown_2.32     
#> 
#> loaded via a namespace (and not attached):
#>  [1] sass_0.4.10                 generics_0.1.4             
#>  [3] SparseArray_1.13.3          lattice_0.23-1             
#>  [5] digest_0.6.39               magrittr_2.0.5             
#>  [7] evaluate_1.0.5              grid_4.6.1                 
#>  [9] RColorBrewer_1.1-3          fastmap_1.2.0              
#> [11] jsonlite_2.0.0              Matrix_1.7-6               
#> [13] scales_1.4.0                jquerylib_0.1.4            
#> [15] abind_1.4-8                 cli_3.6.6                  
#> [17] rlang_1.3.0                 XVector_0.53.0             
#> [19] Biobase_2.73.2              withr_3.0.3                
#> [21] cachem_1.1.0                DelayedArray_0.39.7        
#> [23] yaml_2.3.12                 otel_0.2.0                 
#> [25] S4Arrays_1.13.1             tools_4.6.1                
#> [27] dplyr_1.2.1                 ggplot2_4.0.3              
#> [29] SummarizedExperiment_1.43.0 BiocGenerics_0.59.12       
#> [31] buildtools_1.0.0            vctrs_0.7.3                
#> [33] R6_2.6.1                    matrixStats_1.5.0          
#> [35] stats4_4.6.1                lifecycle_1.0.5            
#> [37] Seqinfo_1.3.2               S4Vectors_0.51.10          
#> [39] IRanges_2.47.5              pkgconfig_2.0.3            
#> [41] pillar_1.11.1               bslib_0.12.0               
#> [43] gtable_0.3.6                glue_1.8.1                 
#> [45] tidyselect_1.2.1            tibble_3.3.1               
#> [47] xfun_0.61                   GenomicRanges_1.65.4       
#> [49] sys_3.4.3                   MatrixGenerics_1.25.0      
#> [51] knitr_1.52                  farver_2.1.2               
#> [53] htmltools_0.5.9             labeling_0.4.3             
#> [55] maketools_1.3.2             compiler_4.6.1             
#> [57] S7_0.2.2