Quick Start Guide

Introduction

scPAS (Single-Cell Phenotype-Associated Subpopulation identifier) is a computational tool designed to identify cell subpopulations associated with phenotypes by integrating single-cell RNA-seq data with bulk transcriptomics data.

Key Features

  • Multi-modal Integration: Combines single-cell and bulk RNA-seq data
  • Multiple Phenotype Types: Supports continuous, binary, and survival phenotypes
  • Network Regularization: Leverages gene-gene similarity networks
  • Statistical Rigor: Permutation-based significance testing with FDR correction

Package Installation

# Install from GitHub
if (!require("devtools")) install.packages("devtools")
devtools::install_github("Zaoqu-Liu/scPAS")

# Install dependencies if needed
if (!require("BiocManager")) install.packages("BiocManager")
BiocManager::install("preprocessCore")

Quick Example

Load Required Packages

library(scPAS)
library(Matrix)
library(Seurat)

Simulate Example Data

For this quick start, we’ll create simulated data to demonstrate the workflow:

set.seed(42)

# Simulate bulk RNA-seq data (500 genes x 50 samples)
n_genes <- 500
n_bulk_samples <- 50
n_cells <- 200

bulk_data <- matrix(
  rpois(n_genes * n_bulk_samples, lambda = 100),
  nrow = n_genes,
  ncol = n_bulk_samples
)
rownames(bulk_data) <- paste0("Gene", 1:n_genes)
colnames(bulk_data) <- paste0("Sample", 1:n_bulk_samples)

# Add log transformation
bulk_data <- log2(bulk_data + 1)

# Simulate single-cell data (same genes x 200 cells)
sc_counts <- matrix(
  rpois(n_genes * n_cells, lambda = 5),
  nrow = n_genes,
  ncol = n_cells
)
rownames(sc_counts) <- paste0("Gene", 1:n_genes)
colnames(sc_counts) <- paste0("Cell", 1:n_cells)

# Create Seurat object
sc_obj <- CreateSeuratObject(
  counts = sc_counts,
  project = "QuickStart"
)

# Add cell type labels
sc_obj$celltype <- sample(
  c("TypeA", "TypeB", "TypeC"),
  n_cells,
  replace = TRUE
)

# Simulate phenotype (continuous)
phenotype <- rnorm(n_bulk_samples, mean = 50, sd = 10)
names(phenotype) <- colnames(bulk_data)

Preprocess Single-Cell Data

Use the built-in run_Seurat() function for standard preprocessing:

# Standard Seurat preprocessing
sc_obj <- run_Seurat(sc_obj, verbose = FALSE)

# Check the result
sc_obj
#> An object of class Seurat 
#> 500 features across 200 samples within 1 assay 
#> Active assay: RNA (500 features, 500 variable features)
#>  3 layers present: counts, data, scale.data
#>  3 dimensional reductions calculated: pca, tsne, umap

Run scPAS Analysis

# Run scPAS with Gaussian family (continuous phenotype)
result <- scPAS(
  bulk_dataset = bulk_data,
  sc_dataset = sc_obj,
  phenotype = phenotype,
  family = "gaussian",
  nfeature = 200,           # Use top 200 variable genes
  permutation_times = 100,  # Reduced for demo (use 1000+ in practice)
  do_imputation = FALSE,    # Skip imputation for speed
  n_cores = 1               # Single core
)

Examine Results

# View added metadata columns
head(result@meta.data[, c("scPAS_RS", "scPAS_NRS", "scPAS_Pvalue", "scPAS_FDR", "scPAS")])
#>       scPAS_RS scPAS_NRS scPAS_Pvalue scPAS_FDR scPAS
#> Cell1        0         0            1         1     0
#> Cell2        0         0            1         1     0
#> Cell3        0         0            1         1     0
#> Cell4        0         0            1         1     0
#> Cell5        0         0            1         1     0
#> Cell6        0         0            1         1     0

# Summary of cell classifications
table(result$scPAS)
#> 
#>   0 
#> 200

# Check significance
cat("Cells with FDR < 0.05:", sum(result$scPAS_FDR < 0.05, na.rm = TRUE), "\n")
#> Cells with FDR < 0.05: 0
cat("scPAS+ cells:", sum(result$scPAS == "scPAS+", na.rm = TRUE), "\n")
#> scPAS+ cells: 0
cat("scPAS- cells:", sum(result$scPAS == "scPAS-", na.rm = TRUE), "\n")
#> scPAS- cells: 0

Basic Visualization

library(ggplot2)

# UMAP plot colored by cell type
p1 <- DimPlot(result, group.by = "celltype", label = TRUE) +
  ggtitle("Cell Types") +
  theme(legend.position = "bottom")

# UMAP plot colored by risk score
p2 <- FeaturePlot(result, features = "scPAS_NRS") +
  scale_color_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
  ggtitle("Normalized Risk Score")

# Combine plots
p1 | p2

Output Structure

The scPAS function adds the following columns to the Seurat object’s metadata:

Column Description
scPAS_RS Raw risk score
scPAS_NRS Normalized risk score (Z-statistic)
scPAS_Pvalue P-value from permutation test
scPAS_FDR FDR-adjusted p-value
scPAS Classification: “scPAS+”, “scPAS-”, or “0”

Three Phenotype Types

1. Continuous Phenotype (Gaussian)

For continuous outcomes like age, BMI, gene expression levels:

result <- scPAS(
  bulk_dataset = bulk_data,
  sc_dataset = sc_obj,
  phenotype = continuous_values,
  family = "gaussian"
)

2. Binary Phenotype (Binomial)

For case-control, responder/non-responder comparisons:

# Binary phenotype (0/1)
binary_phenotype <- c(0, 1, 0, 1, 1, ...)

result <- scPAS(
  bulk_dataset = bulk_data,
  sc_dataset = sc_obj,
  phenotype = binary_phenotype,
  family = "binomial",
  tag = c("Control", "Case")  # Labels for 0 and 1
)

3. Survival Phenotype (Cox)

For time-to-event data:

# Create survival object
library(survival)
surv_phenotype <- Surv(time = survival_times, event = event_status)

result <- scPAS(
  bulk_dataset = bulk_data,
  sc_dataset = sc_obj,
  phenotype = surv_phenotype,
  family = "cox"
)

Next Steps

  • Algorithm Details: See vignette("algorithm") for methodology
  • Visualization: See vignette("visualization") for advanced plots
  • Case Studies: See vignette("case-survival") for real-world examples
  • Full Tutorial: See vignette("scPAS_Tutorial") for comprehensive guide

Session Information

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] future_1.75.0      survminer_0.5.2    ggpubr_1.0.0       survival_3.8-9    
#>  [5] dplyr_1.2.1        patchwork_1.3.2    RColorBrewer_1.1-3 Seurat_5.5.1      
#>  [9] SeuratObject_5.4.0 sp_2.2-3           scPAS_1.0.4        Matrix_1.7-5      
#> [13] ggplot2_4.0.3      rmarkdown_2.31    
#> 
#> loaded via a namespace (and not attached):
#>   [1] sys_3.4.3              jsonlite_2.0.0         magrittr_2.0.5        
#>   [4] spatstat.utils_3.2-4   farver_2.1.2           vctrs_0.7.3           
#>   [7] ROCR_1.0-12            spatstat.explore_3.8-1 rstatix_1.0.0         
#>  [10] htmltools_0.5.9        broom_1.0.13           Formula_1.2-5         
#>  [13] sass_0.4.10            sctransform_0.4.3      parallelly_1.48.0     
#>  [16] KernSmooth_2.23-26     bslib_0.11.0           htmlwidgets_1.6.4     
#>  [19] ica_1.0-3              plyr_1.8.9             plotly_4.12.0         
#>  [22] zoo_1.8-15             cachem_1.1.0           commonmark_2.0.0      
#>  [25] buildtools_1.0.0       igraph_2.3.3           mime_0.13             
#>  [28] lifecycle_1.0.5        pkgconfig_2.0.3        R6_2.6.1              
#>  [31] fastmap_1.2.0          fitdistrplus_1.2-6     shiny_1.14.0          
#>  [34] digest_0.6.39          tensor_1.5.1           RSpectra_0.16-2       
#>  [37] irlba_2.3.7            labeling_0.4.3         progressr_1.0.0       
#>  [40] spatstat.sparse_3.2-0  httr_1.4.8             polyclip_1.10-7       
#>  [43] abind_1.4-8            compiler_4.6.1         withr_3.0.3           
#>  [46] S7_0.2.2               backports_1.5.1        carData_3.0-6         
#>  [49] fastDummies_1.7.6      ggsignif_0.6.4         MASS_7.3-66           
#>  [52] tools_4.6.1            lmtest_0.9-40          otel_0.2.0            
#>  [55] httpuv_1.6.17          future.apply_1.20.2    goftest_1.2-3         
#>  [58] glue_1.8.1             nlme_3.1-170           promises_1.5.0        
#>  [61] gridtext_0.1.6         grid_4.6.1             Rtsne_0.17            
#>  [64] cluster_2.1.8.2        reshape2_1.4.5         generics_0.1.4        
#>  [67] gtable_0.3.6           spatstat.data_3.1-9    preprocessCore_1.75.0 
#>  [70] tidyr_1.3.2            data.table_1.18.4      xml2_1.6.0            
#>  [73] car_3.1-5              spatstat.geom_3.8-1    RcppAnnoy_0.0.23      
#>  [76] markdown_2.0           ggrepel_0.9.8          RANN_2.6.2            
#>  [79] pillar_1.11.1          stringr_1.6.0          spam_2.11-4           
#>  [82] RcppHNSW_0.7.0         later_1.4.8            splines_4.6.1         
#>  [85] ggtext_0.1.2           lattice_0.22-9         deldir_2.0-4          
#>  [88] tidyselect_1.2.1       maketools_1.3.2        miniUI_0.1.2          
#>  [91] pbapply_1.7-4          knitr_1.51             gridExtra_2.3.1       
#>  [94] litedown_0.10          scattermore_1.2        xfun_0.60             
#>  [97] matrixStats_1.5.0      stringi_1.8.7          lazyeval_0.2.3        
#> [100] yaml_2.3.12            evaluate_1.0.5         codetools_0.2-20      
#> [103] tibble_3.3.1           cli_3.6.6              uwot_0.2.4            
#> [106] xtable_1.8-8           reticulate_1.46.0      jquerylib_0.1.4       
#> [109] Rcpp_1.1.2             globals_0.19.1         spatstat.random_3.5-0 
#> [112] png_0.1-9              spatstat.univar_3.2-0  parallel_4.6.1        
#> [115] dotCall64_1.2          listenv_1.0.0          viridisLite_0.4.3     
#> [118] scales_1.4.0           ggridges_0.5.7         purrr_1.2.2           
#> [121] rlang_1.3.0            cowplot_1.2.0