Skip to contents

Overview

Three functions cover the co-expression workflow end-to-end:

  • CorrelateGene() - correlate one gene of interest against every other gene, within each cell type, at single-cell, pseudobulk, or group-average resolution.
  • PlotCorrelation() - visualize the results as per-cell-type volcano-style plots (r vs. -log10 adjusted p-value).
  • SplitByGene() - label each cell as high / low (or expressed / zero) based on a gene’s expression, per cell type. Feeds directly into RunGSEA_pseudobulk() or FindMarkers().
#for this we will load an object with multiple donors/samples
#InstallData("panc8")
data("panc8")
panc8 <- UpdateSeuratObject(panc8)
panc8 <- NormalizeData(panc8, verbose = FALSE)
panc8$donor=panc8$orig.ident
panc8 <- PrepObject(panc8,
  variables   = c("donor", "celltype"),
  group.by    = "celltype",
  object_name = "panc8")

1. CorrelateGene() - single-cell level

The default level = "sc" computes Spearman correlation across individual cells within each cell type. Only genes expressed in at least min_pct% of cells in a given cell type are tested (expressed_only = TRUE by default).

cor_sc <- CorrelateGene(panc8,
  gene     = "STAT1",
  group.by = "celltype",
  level    = "sc")

head(cor_sc)
##           gene cell_type          r            p         padj   n   goi level
## 1         NME2    acinar -0.3079248 2.033663e-18 2.327934e-14 772 STAT1    sc
## 2 LOC100506281    acinar -0.2992826 1.935724e-17 1.107912e-13 772 STAT1    sc
## 3       MAN1B1    acinar -0.2945833 6.386592e-17 1.982662e-13 772 STAT1    sc
## 4        TAAR5    acinar -0.2942597 6.928145e-17 1.982662e-13 772 STAT1    sc
## 5     EIF4EBP3    acinar -0.2860819 5.236258e-16 1.192260e-12 772 STAT1    sc
## 6          A2M    acinar -0.2853201 6.300764e-16 1.192260e-12 772 STAT1    sc
##     method
## 1 spearman
## 2 spearman
## 3 spearman
## 4 spearman
## 5 spearman
## 6 spearman

Each row is one gene in one cell type. Key columns:

Column Description
gene Correlated gene
cell_type Cell-type group
r Spearman (or Pearson) correlation with the gene of interest
p Raw p-value
padj BH-adjusted p-value
n Number of cells tested
goi Gene of interest
level Resolution used ("sc", "pseudobulk", or "group")

2. PlotCorrelation() - visualize results

PlotCorrelation() draws one panel per cell type showing the top n_pos positively and top n_neg negatively correlated genes. Significant genes (padj < padj_cutoff) are colored; the top label_n in each direction are labeled. Panels are assembled with patchwork::wrap_plots().

PlotCorrelation(cor_sc,
  n_pos       = 20,
  n_neg       = 20,
  label_n     = 5,
  padj_cutoff = 0.05,
  r_cutoff    = 0.3,     # draws ±0.3 dotted reference lines
  ncol        = 4)

Restrict to specific cell types with cell.type:

PlotCorrelation(cor_sc,
  cell.type = c("acinar", "ductal"),
  n_pos     = 15,
  n_neg     = 15,
  label_n   = 6)

Save to PDF (a .legend sidecar is written automatically). width/height override the auto-calculated PDF size, and timestamp = TRUE appends a _YYYYMMDD-HHMMSS stamp to the saved file name so repeated runs are versioned instead of overwritten:

PlotCorrelation(cor_sc,
  n_pos      = 20,
  n_neg      = 20,
  output_dir = "./Figures",
  file_name  = "STAT1_coexpression",
  timestamp  = TRUE)

3. CorrelateGene() - pseudobulk level

For multi-donor datasets, pseudobulk correlation (level = "pseudobulk") aggregates counts to the donor x cell-type level first. This removes within-donor cell-count inflation and is more appropriate for multi-sample comparisons. Requires sample.by.

cor_pb <- CorrelateGene(panc8,
  gene       = "STAT1",
  group.by   = "celltype",
  level      = "pseudobulk",
  sample.by  = "donor",
  min_donors = 3)

Group-average level

level = "group" averages expression within each combination of cell type and condition.by, then correlates across those group-level averages. Useful when donors are few but groups are many (e.g. comparing many cell-type x timepoint combinations).

cor_grp <- CorrelateGene(panc8,
  gene         = "STAT1",
  group.by     = "celltype",
  level        = "sc",
  condition.by = "donor",
  min_groups   = 3)

head(cor_grp)
##           gene cell_type          r            p         padj   n   goi level
## 1         NME2    acinar -0.3079248 2.033663e-18 2.327934e-14 772 STAT1    sc
## 2 LOC100506281    acinar -0.2992826 1.935724e-17 1.107912e-13 772 STAT1    sc
## 3       MAN1B1    acinar -0.2945833 6.386592e-17 1.982662e-13 772 STAT1    sc
## 4        TAAR5    acinar -0.2942597 6.928145e-17 1.982662e-13 772 STAT1    sc
## 5     EIF4EBP3    acinar -0.2860819 5.236258e-16 1.192260e-12 772 STAT1    sc
## 6          A2M    acinar -0.2853201 6.300764e-16 1.192260e-12 772 STAT1    sc
##     method
## 1 spearman
## 2 spearman
## 3 spearman
## 4 spearman
## 5 spearman
## 6 spearman

4. Stratified analysis with compare.by

Pass compare.by to run the correlation separately within each level of a grouping variable (e.g. sex, condition, batch). The result gains a compare_group column, and PlotCorrelation() automatically lays out one column per comparison group.

cor_by_dataset <- CorrelateGene(panc8,
  gene       = "STAT1",
  group.by   = "celltype",
  level      = "sc",
  compare.by = "dataset")

# ncol defaults to the number of comparison groups
PlotCorrelation(cor_by_dataset,
  cell.type = "acinar",
  n_pos     = 15,
  n_neg     = 15,ncol = 3)


5. SplitByGene() - label cells for downstream analysis

SplitByGene() adds a new metadata column splitting cells into expression groups within each cell type. The column plugs straight into RunGSEA_pseudobulk() (as contrast.by) or Seurat::FindMarkers().

Default: zero vs. expressed

panc8 <- SplitByGene(panc8,
  gene     = "STAT1",
  group.by = "celltype",
  split    = "zero_vs_expressed")
##            celltype
## STAT1_split acinar activated_stellate alpha beta delta ductal endothelial
##   expressed    772                227  2209 1393   359   1065          43
##   zero        1092                247  2406 2286   654    889         253
##            celltype
## STAT1_split epsilon gamma macrophage mast quiescent_stellate schwann
##   expressed      15   306         44   11                 36       8
##   zero           15   319         35   45                144      17
# New column "STAT1_split" is now in meta.data:
table(panc8$STAT1_split, panc8$celltype, useNA = "ifany")
##            
##             acinar activated_stellate alpha beta delta ductal endothelial
##   expressed    772                227  2209 1393   359   1065          43
##   zero        1092                247  2406 2286   654    889         253
##            
##             epsilon gamma macrophage mast quiescent_stellate schwann
##   expressed      15   306         44   11                 36       8
##   zero           15   319         35   45                144      17

Quantile split

panc8 <- SplitByGene(panc8,
  gene           = "STAT1",
  group.by       = "celltype",
  split          = "quantile",
  quantile_range = c(0.25, 0.75))  # bottom 25% = "low", top 25% = "high"

Middle cells (between the two quantiles) receive NA and are excluded from downstream tests, keeping only the contrast extremes.

Feeding into RunGSEA_pseudobulk()

RunGSEA_pseudobulk(panc8,
  group.by    = "celltype",
  sample.by   = "donor",
  contrast.by = "STAT1_split",
  pathway_sets = list( Hallmark = list(category = "H")),
  species     = "human",
  output_dir  = "./Figures/STAT1_pseudobulk_GSEA")

Feeding into RunWilcoxAUC()

markers <- RunWilcoxAUC(panc8,
  group.by  = "STAT1_split",split.by="celltype",
  min.pct   = 0.1,
  logfc.threshold = 0.25)
head(markers)
##   celltype  feature     group      avgExpr         logFC statistic       auc
## 1    gamma A1BG-AS1 expressed 0.0086168257  0.0073175175   50598.5 0.5183529
## 2    gamma     A1BG expressed 0.2699636005  0.1362715557   62992.0 0.6453173
## 3    gamma     A1CF expressed 0.5195332278  0.1377585800   59419.0 0.6087139
## 4    gamma  A2M-AS1 expressed 0.0139130153  0.0067855068   50098.0 0.5132256
## 5    gamma    A2ML1 expressed 0.0052403089 -0.0005868226   50115.0 0.5133997
## 6    gamma    A2MP1 expressed 0.0001736141  0.0001736141   48966.5 0.5016340
##           pval         padj     pct_in   pct_out
## 1 7.617306e-03 2.366026e-02  4.9019608  1.253918
## 2 1.376288e-13 1.060389e-11 50.3267974 19.749216
## 3 4.934176e-07 4.048545e-06 64.3790850 36.050157
## 4 3.659151e-02 9.559750e-02  3.9215686  1.253918
## 5 5.738417e-02 1.394056e-01  4.5751634  1.880878
## 6 3.087634e-01 5.437977e-01  0.3267974  0.000000

See also


Session info

## R version 4.3.1 (2023-06-16)
## Platform: aarch64-apple-darwin20 (64-bit)
## Running under: macOS 15.6
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: America/Chicago
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] stxBrain.SeuratData_0.1.2 pbmc3k.SeuratData_3.1.4  
##  [3] panc8.SeuratData_3.0.2    ifnb.SeuratData_3.1.0    
##  [5] bmcite.SeuratData_0.3.0   SeuratData_0.2.2         
##  [7] Seurat_5.2.1              SeuratObject_5.1.0       
##  [9] sp_2.1-3                  scSidekick_1.2.0         
## 
## loaded via a namespace (and not attached):
##   [1] RColorBrewer_1.1-3     rstudioapi_0.16.0      jsonlite_2.0.0        
##   [4] magrittr_2.0.3         spatstat.utils_3.1-0   farver_2.1.2          
##   [7] rmarkdown_2.29         fs_1.6.6               ragg_1.4.0            
##  [10] vctrs_0.6.5            ROCR_1.0-11            spatstat.explore_3.2-7
##  [13] htmltools_0.5.8.1      sass_0.4.10            sctransform_0.4.1     
##  [16] parallelly_1.37.1      KernSmooth_2.23-22     bslib_0.9.0           
##  [19] htmlwidgets_1.6.4      desc_1.4.3             ica_1.0-3             
##  [22] plyr_1.8.9             plotly_4.10.4          zoo_1.8-14            
##  [25] cachem_1.1.0           igraph_2.0.3           mime_0.13             
##  [28] lifecycle_1.0.4        pkgconfig_2.0.3        Matrix_1.6-5          
##  [31] R6_2.6.1               fastmap_1.2.0          fitdistrplus_1.1-11   
##  [34] future_1.33.2          shiny_1.11.1           digest_0.6.37         
##  [37] colorspace_2.1-1       patchwork_1.3.1        tensor_1.5            
##  [40] RSpectra_0.16-1        irlba_2.3.5.1          textshaping_0.3.7     
##  [43] labeling_0.4.3         progressr_0.14.0       spatstat.sparse_3.0-3 
##  [46] httr_1.4.7             polyclip_1.10-6        abind_1.4-8           
##  [49] compiler_4.3.1         withr_3.0.2            S7_0.2.0              
##  [52] fastDummies_1.7.3      highr_0.10             MASS_7.3-60.0.1       
##  [55] rappdirs_0.3.3         tools_4.3.1            lmtest_0.9-40         
##  [58] otel_0.2.0             httpuv_1.6.16          future.apply_1.11.2   
##  [61] goftest_1.2-3          glue_1.8.0             nlme_3.1-164          
##  [64] promises_1.5.0         grid_4.3.1             Rtsne_0.17            
##  [67] cluster_2.1.6          reshape2_1.4.4         generics_0.1.4        
##  [70] gtable_0.3.6           spatstat.data_3.0-4    tidyr_1.3.1           
##  [73] data.table_1.15.4      spatstat.geom_3.2-9    RcppAnnoy_0.0.22      
##  [76] ggrepel_0.9.6          RANN_2.6.1             pillar_1.11.0         
##  [79] stringr_1.5.1          limma_3.58.1           spam_2.10-0           
##  [82] RcppHNSW_0.6.0         later_1.4.2            splines_4.3.1         
##  [85] dplyr_1.1.4            lattice_0.22-6         survival_3.5-8        
##  [88] deldir_2.0-4           tidyselect_1.2.1       locfit_1.5-9.9        
##  [91] miniUI_0.1.1.1         pbapply_1.7-2          knitr_1.45            
##  [94] gridExtra_2.3          edgeR_4.0.16           scattermore_1.2       
##  [97] xfun_0.52              statmod_1.5.0          matrixStats_1.2.0     
## [100] stringi_1.8.7          lazyeval_0.2.2         yaml_2.3.10           
## [103] evaluate_0.23          codetools_0.2-20       tibble_3.3.0          
## [106] cli_3.6.5              uwot_0.1.16            xtable_1.8-4          
## [109] reticulate_1.42.0      systemfonts_1.3.2      jquerylib_0.1.4       
## [112] dichromat_2.0-0.1      Rcpp_1.1.0             globals_0.16.3        
## [115] spatstat.random_3.2-3  png_0.1-8              parallel_4.3.1        
## [118] pkgdown_2.1.3          ggplot2_4.0.3          presto_1.0.0          
## [121] dotCall64_1.1-1        listenv_0.9.1          viridisLite_0.4.2     
## [124] scales_1.4.0           ggridges_0.5.6         purrr_1.0.4           
## [127] crayon_1.5.2           rlang_1.1.6            cowplot_1.2.0.9000