Skip to contents

Overview

Differential connectivity analysis enables comparison of cell-cell communication networks between conditions (e.g., disease vs. healthy, treated vs. control). This vignette demonstrates the complete workflow for identifying altered signaling pathways.

Workflow

┌─────────────────┐     ┌─────────────────┐
│   Condition 1   │     │   Condition 2   │
│  (Reference)    │     │    (Test)       │
└────────┬────────┘     └────────┬────────┘
         │                       │
         ▼                       ▼
┌─────────────────┐     ┌─────────────────┐
│CreateConnectome │     │CreateConnectome │
└────────┬────────┘     └────────┬────────┘
         │                       │
         └───────────┬───────────┘
                     ▼
         ┌───────────────────────┐
         │DifferentialConnectome │
         └───────────┬───────────┘
                     │
         ┌───────────┴───────────┐
         ▼                       ▼
┌─────────────────┐     ┌─────────────────┐
│  Visualization  │     │   Statistics    │
└─────────────────┘     └─────────────────┘

Step 1: Prepare Data

Split by Condition

library(Seurat)
library(Connectome)

# Method 1: SplitObject
seurat_list <- SplitObject(seurat_obj, split.by = "condition")
seurat_ctrl <- seurat_list[["control"]]
seurat_treat <- seurat_list[["treatment"]]

# Method 2: EvenSplit (balanced sampling)
seurat_list <- EvenSplit(seurat_obj, split.by = "condition")
seurat_ctrl <- seurat_list[["control"]]
seurat_treat <- seurat_list[["treatment"]]

Why EvenSplit?

EvenSplit() ensures each cell type has equal representation across conditions, preventing bias from unequal cell numbers:

# Without EvenSplit: potential bias
# Control: 1000 T cells, 500 B cells
# Treatment: 200 T cells, 800 B cells

# With EvenSplit: balanced
# Control: 200 T cells, 500 B cells
# Treatment: 200 T cells, 500 B cells

Step 2: Create Individual Connectomes

# Reference connectome (control)
conn_ctrl <- CreateConnectome(
  object = seurat_ctrl,
  species = "human",
  LR.database = "fantom5",
  min.cells.per.ident = 30,
  p.values = FALSE  # Optional for differential analysis
)

# Test connectome (treatment)
conn_treat <- CreateConnectome(
  object = seurat_treat,
  species = "human",
  LR.database = "fantom5",
  min.cells.per.ident = 30,
  p.values = FALSE
)

Important: Both connectomes must have: - Same cell type identities - Same ligand-receptor pairs - Matching edge identifiers

Step 3: Compute Differential Connectome

diff_conn <- DifferentialConnectome(
  connect.ref = conn_ctrl,
  connect.test = conn_treat,
  min.pct = 0.1
)

Output Columns

Column Description
ligand.norm.lfc Log2 fold change of ligand expression
recept.norm.lfc Log2 fold change of receptor expression
weight.norm.lfc Log2 fold change of edge weight
pct.source.1 % source cells expressing ligand (reference)
pct.source.2 % source cells expressing ligand (test)
pct.target.1 % target cells expressing receptor (reference)
pct.target.2 % target cells expressing receptor (test)
score Perturbation score =

Step 4: Interpret Results

Perturbation Score

The score captures edges where both ligand and receptor are differentially expressed:

Score=|log2(FCL)|×|log2(FCR)|\text{Score} = |\log_2(\text{FC}_L)| \times |\log_2(\text{FC}_R)|

  • High score: Strong coordinated change
  • Score = 0: No change or unilateral change

Example Interpretation

# Top perturbed edges
top_edges <- diff_conn[order(-diff_conn$score), ][1:20, ]

# Upregulated signaling (both components increased)
upregulated <- subset(diff_conn, 
                      ligand.norm.lfc > 0 & recept.norm.lfc > 0 & score > 1)

# Downregulated signaling (both components decreased)
downregulated <- subset(diff_conn,
                        ligand.norm.lfc < 0 & recept.norm.lfc < 0 & score > 1)

# Rewired signaling (opposite direction changes)
rewired <- subset(diff_conn,
                  (ligand.norm.lfc > 0 & recept.norm.lfc < 0) |
                  (ligand.norm.lfc < 0 & recept.norm.lfc > 0))

Step 5: Visualization

Circos Diagram

CircosDiff(
  diff_conn,
  min.score = 1,
  min.pct = 0.1,
  sources.include = NULL,  # All sources
  targets.include = NULL,  # All targets
  title = "Differential Connectivity: Treatment vs Control"
)

Edge Dot Plot

DiffEdgeDotPlot(
  diff_conn,
  min.score = 0.5,
  features = c("VEGFA", "IL6", "TNF", "CXCL12")
)

Scoring Heatmap

# Unaligned view (separate ligand/receptor panels)
DifferentialScoringPlot(
  diff_conn,
  min.score = 0.5,
  aligned = FALSE
)

# Aligned view (edge-matched)
DifferentialScoringPlot(
  diff_conn,
  sources.include = c("Fibroblast", "Macrophage"),
  targets.include = c("Epithelial", "Endothelial"),
  min.score = 0.5,
  aligned = TRUE
)

Advanced Analysis

Mode-Specific Changes

# Analyze by signaling mode
modes <- unique(diff_conn$mode)
mode_summary <- data.frame()

for (m in modes) {
  subset_m <- diff_conn[diff_conn$mode == m, ]
  mode_summary <- rbind(mode_summary, data.frame(
    mode = m,
    n_edges = nrow(subset_m),
    mean_score = mean(subset_m$score, na.rm = TRUE),
    max_score = max(subset_m$score, na.rm = TRUE),
    n_upregulated = sum(subset_m$ligand.norm.lfc > 0 & 
                        subset_m$recept.norm.lfc > 0, na.rm = TRUE),
    n_downregulated = sum(subset_m$ligand.norm.lfc < 0 & 
                          subset_m$recept.norm.lfc < 0, na.rm = TRUE)
  ))
}

# Sort by mean perturbation score
mode_summary <- mode_summary[order(-mode_summary$mean_score), ]
head(mode_summary, 10)

Cell Type-Specific Changes

# Identify most affected cell types (as senders)
sender_changes <- aggregate(score ~ source, data = diff_conn, 
                           FUN = function(x) c(mean = mean(x), max = max(x)))

# Identify most affected cell types (as receivers)
receiver_changes <- aggregate(score ~ target, data = diff_conn,
                             FUN = function(x) c(mean = mean(x), max = max(x)))

Export Results

# Export significant differential edges
sig_edges <- subset(diff_conn, score > 1)
write.csv(sig_edges, "differential_edges.csv", row.names = FALSE)

# Export for Cytoscape
cytoscape_format <- diff_conn[, c("source", "target", "ligand", "receptor", 
                                   "ligand.norm.lfc", "recept.norm.lfc", "score")]
write.csv(cytoscape_format, "cytoscape_import.csv", row.names = FALSE)

Best Practices

Sample Size

  • Minimum 30 cells per identity per condition
  • Use EvenSplit() for balanced comparisons
  • Consider bootstrapping for small samples

Filtering Strategy

# Stringent: High confidence changes
high_conf <- subset(diff_conn,
  score > 2 &
  (pct.source.1 > 0.1 | pct.source.2 > 0.1) &
  (pct.target.1 > 0.1 | pct.target.2 > 0.1)
)

# Discovery: Explore all potential changes
discovery <- subset(diff_conn,
  score > 0.5 &
  (pct.source.1 > 0.05 | pct.source.2 > 0.05)
)

Handling Infinite Values

When expression goes from 0 to positive (or vice versa), fold change is infinite. Connectome handles this automatically:

# Automatic handling in visualization functions
CircosDiff(diff_conn, infinity.to.max = TRUE)
DiffEdgeDotPlot(diff_conn, infinity.to.max = TRUE)

Common Pitfalls

  1. Batch effects: Ensure conditions are not confounded with batches
  2. Cell type composition: Changes in cell proportions can affect results
  3. Pseudoreplication: Multiple samples from same individual should be aggregated
  4. Multiple testing: Consider adjusting score thresholds for genome-wide comparisons

Session Info

sessionInfo()
#> R version 4.4.0 (2024-04-24)
#> Platform: aarch64-apple-darwin20
#> Running under: macOS 15.6.1
#> 
#> Matrix products: default
#> BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
#> LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0
#> 
#> locale:
#> [1] C
#> 
#> time zone: Asia/Shanghai
#> tzcode source: internal
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> loaded via a namespace (and not attached):
#>  [1] digest_0.6.39     desc_1.4.3        R6_2.6.1          fastmap_1.2.0    
#>  [5] xfun_0.56         cachem_1.1.0      knitr_1.51        htmltools_0.5.9  
#>  [9] rmarkdown_2.30    lifecycle_1.0.5   cli_3.6.5         sass_0.4.10      
#> [13] pkgdown_2.1.3     textshaping_1.0.4 jquerylib_0.1.4   systemfonts_1.3.1
#> [17] compiler_4.4.0    tools_4.4.0       ragg_1.5.0        bslib_0.9.0      
#> [21] evaluate_1.0.5    yaml_2.3.12       otel_0.2.0        jsonlite_2.0.0   
#> [25] rlang_1.1.7       fs_1.6.6          htmlwidgets_1.6.4