Shared root

The object

The labels are shortened first: the path_ prefix is dropped and the link gets the separator _links_, the default of sc.restruct(). To keep the vignette fast, the analysis uses the first 200 of the 501 genes.

cell_data <- as.data.frame(colData(multi.lin.sce))
cell_data$group <- gsub("path_", "", cell_data$group, fixed = TRUE)
cell_data$group <- gsub("|", "_links_", cell_data$group, fixed = TRUE)
table(cell_data$group)
#> 
#>            root            Y_15            Y_18 Y_18_links_Y_15            Y_52 
#>             453             307             676              43             498

genes <- rownames(multi.lin.sce)[seq_len(200)]
scmp_ob <- create_scmp(
    counts = counts(multi.lin.sce)[genes, ],
    cell_data = cell_data,
    ptime_col = "Monocle3_Pseudotime",
    path_col = "group"
)

Each segment as a path

The first way treats the root, the link and the three end paths as five paths. sc.squeeze() bins each of them along its own pseudotime, so the bins of every segment start at 1:

seg_ob <- sc.squeeze(scmp_ob)
plotBinTile(seg_ob)

Tile plot of the bins of the five segments, each binned from 1 along its own pseudotime.

The model then compares each segment with a reference segment. With the root as the reference, the terms of every other segment describe how it differs from the root, at the same binned pseudotime. reference names it; without it, sc.set.poly() takes the first path in C-locale (byte) order, here Y_15, whose uppercase Y sorts before the lowercase r of root.

seg_ob <- sc.set.poly(seg_ob, poly_degree = 3, reference = "root")
seg_ob <- sc.p.vector(seg_ob, verbose = FALSE)
seg_ob <- sc.t.fit(seg_ob, verbose = FALSE)
seg_ob <- sc.filter(seg_ob, rsq = 0.6, vars = "groups")
seg_res <- scmpResults(seg_ob)
seg_lists <- as.data.frame(seg_res[, grep("^sig_", colnames(seg_res))])
colSums(seg_lists)
#>            sig_Y_15vsroot            sig_Y_18vsroot sig_Y_18_links_Y_15vsroot 
#>                       120                       120                        97 
#>            sig_Y_52vsroot                  sig_root 
#>                       110                        56

sig_root holds the genes that change along the root, and sig_Y_52vsroot, for example, the genes whose profile in Y_52 differs from that in the root. The overlaps of the lists sort the genes by the segments in which they differ from the root:

plotIntersect(seg_ob)

UpSet plot of the gene lists of the five segments with the root as the reference.

For example, 17 genes differ from the root in all three end paths but not in the link: along these genes the link carries on the expression of the root, and the end paths leave it. One of them:

plotTrend(seg_ob, link_like_root[1], points = FALSE, lines = TRUE)

Trend of one gene in the five segments: the link follows the root, and the three end paths differ from it.

This view answers questions about the segments themselves, such as which genes the link turns on, but every segment starts again at binned pseudotime 1, so it does not follow a gene from the root to the end of a lineage.

Choosing the reference path

The dummies of the model compare every path with the reference path, so the reference decides what the coefficients and the "groups" lists of sc.filter() say: with Y_15 as the reference, sig_Y_52vsY_15 lists the genes whose profile in Y_52 differs from that in Y_15. The full model is the same with any reference, only written in other terms, so the p-values of sc.p.vector() and its non-flat genes do not change, up to rounding. With Y_52, the lineage that leaves the root on its own, as the reference:

ref_ob <- sc.set.poly(lin_ob, poly_degree = 3, reference = "Y_52")
showPoly(ref_ob)
#> [1] "beta0 + beta1*Y_15vsY_52 + beta2*Y_18vsY_52 + beta3*scmp_binned_pseudotime + beta4*scmp_binned_pseudotimexY_15 + beta5*scmp_binned_pseudotimexY_18 + beta6*scmp_binned_pseudotime2 + beta7*scmp_binned_pseudotime2xY_15 + beta8*scmp_binned_pseudotime2xY_18 + beta9*scmp_binned_pseudotime3 + beta10*scmp_binned_pseudotime3xY_15 + beta11*scmp_binned_pseudotime3xY_18"
ref_ob <- sc.p.vector(ref_ob, verbose = FALSE)
all.equal(scmpResults(ref_ob)$p_value, lin_res$p_value)
#> [1] TRUE
identical(scmpResults(ref_ob)$non_flat, lin_res$non_flat)
#> [1] TRUE
ref_ob <- sc.t.fit(ref_ob, verbose = FALSE)
ref_ob <- sc.filter(ref_ob, rsq = 0.6, vars = "groups")
ref_res <- scmpResults(ref_ob)
colSums(as.data.frame(ref_res[, grep("^sig_", colnames(ref_res))]))
#> sig_Y_15vsY_52 sig_Y_18vsY_52       sig_Y_52 
#>             70             66             72

A good reference is the path the others are best described against: the root when the segments are compared, a control or the most common fate when lineages are compared. Without reference, the first path is the first level of the path column when it is a factor, and otherwise the first label in C-locale (byte) order, in every locale; after sc.restruct(), the first path of end_node_list. Naming the reference keeps an analysis the same when the labels change.

Session information

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] stats4    stats     graphics  grDevices utils     datasets  methods  
#> [8] base     
#> 
#> other attached packages:
#>  [1] ggplot2_4.0.3               SingleCellExperiment_1.35.2
#>  [3] SummarizedExperiment_1.43.0 Biobase_2.73.2             
#>  [5] GenomicRanges_1.65.4        Seqinfo_1.3.2              
#>  [7] IRanges_2.47.5              S4Vectors_0.51.10          
#>  [9] BiocGenerics_0.59.12        generics_0.1.4             
#> [11] MatrixGenerics_1.25.0       matrixStats_1.5.0          
#> [13] maSigPro_1.85.0             scMaSigPro_0.6.0           
#> [15] BiocStyle_2.41.0           
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6        xfun_0.62           bslib_0.12.0       
#>  [4] lattice_0.23-1      vctrs_0.7.3         tools_4.6.1        
#>  [7] parallel_4.6.1      Matrix_1.7-6        RColorBrewer_1.1-3 
#> [10] S7_0.2.2            lifecycle_1.0.5     compiler_4.6.1     
#> [13] farver_2.1.2        codetools_0.2-20    htmltools_0.5.9    
#> [16] sys_3.4.3           buildtools_1.0.0    sass_0.4.10        
#> [19] yaml_2.3.12         venn_1.13           jquerylib_0.1.4    
#> [22] MASS_7.3-66         admisc_0.41         BiocParallel_1.47.0
#> [25] DelayedArray_0.39.8 cachem_1.1.0        abind_1.4-8        
#> [28] mclust_6.1.3        nlme_3.1-171        digest_0.6.39      
#> [31] maketools_1.3.2     labeling_0.4.3      splines_4.6.1      
#> [34] fastmap_1.2.0       grid_4.6.1          cli_3.6.6          
#> [37] SparseArray_1.13.4  patchwork_1.3.2     S4Arrays_1.13.2    
#> [40] withr_3.0.3         scales_1.4.0        rmarkdown_2.32     
#> [43] XVector_0.53.0      otel_0.2.0          gridExtra_2.3.1    
#> [46] evaluate_1.0.5      knitr_1.52          UpSetR_1.4.1       
#> [49] viridisLite_0.4.3   mgcv_1.9-4          rlang_1.3.0        
#> [52] Rcpp_1.1.2          glue_1.8.1          BiocManager_1.30.27
#> [55] jsonlite_2.0.0      plyr_1.8.9          R6_2.6.1

References

Cao, Junyue, Malte Spielmann, Xiaojie Qiu, et al. 2019. “The Single-Cell Transcriptional Landscape of Mammalian Organogenesis.” Nature 566 (7745): 496–502. https://doi.org/10.1038/s41586-019-0969-x.
Saelens, Wouter, Robrecht Cannoodt, Helena Todorov, and Yvan Saeys. 2019. “A Comparison of Single-Cell Trajectory Inference Methods.” Nature Biotechnology 37 (5): 547–54. https://doi.org/10.1038/s41587-019-0071-9.
Van den Berge, Koen, Hector Roux de Bézieux, Kelly Street, et al. 2020. “Trajectory-Based Differential Expression Analysis for Single-Cell Sequencing Data.” Nature Communications 11: 1201. https://doi.org/10.1038/s41467-020-14766-3.