Differential Communication Analysis

Introduction

Differential communication analysis is essential for understanding how cell-cell signaling changes between biological conditions, such as:

  • Disease vs. Normal: Altered communication in cancer, inflammation, or autoimmune diseases
  • Treatment response: Changes after drug treatment or immunotherapy
  • Developmental stages: Communication dynamics during differentiation

NOVA provides comprehensive tools for comparing communication networks between conditions.

Setup

library(NOVA)
library(ggplot2)

Simulating Two Conditions

We’ll create two simulated datasets representing control and treatment conditions.

set.seed(42)

# Common parameters
n_genes <- 200
n_cells <- 400
gene_names <- paste0("Gene", 1:n_genes)
clusters <- c("T_cells", "B_cells", "Macrophages", "Fibroblasts")

# Get LR database genes
lr_db <- GetLRDatabase("lrc2p")
ligands <- unique(lr_db$ligand)[1:25]
receptors <- unique(lr_db$receptor)[1:25]

# Function to create expression matrix
create_expr <- function(n_cells, seed_offset = 0, fold_changes = NULL) {
  set.seed(42 + seed_offset)
  cell_names <- paste0("Cell", 1:n_cells)
  
  expr <- matrix(0, nrow = n_genes, ncol = n_cells,
                 dimnames = list(gene_names, cell_names))
  
  # Base expression
  expressed <- sample(length(expr), size = length(expr) * 0.25)
  expr[expressed] <- abs(rnorm(length(expressed), mean = 2, sd = 1))
  
  # Map genes
  rownames(expr)[1:25] <- ligands
  rownames(expr)[26:50] <- receptors
  
  # Apply fold changes if provided
  if (!is.null(fold_changes)) {
    for (gene in names(fold_changes)) {
      if (gene %in% rownames(expr)) {
        expr[gene, ] <- expr[gene, ] * fold_changes[gene]
      }
    }
  }
  
  # Create annotation
  cluster_assign <- sample(clusters, n_cells, replace = TRUE)
  names(cluster_assign) <- cell_names
  ann <- data.frame(cell = cell_names, cluster = cluster_assign)
  
  list(expr = Matrix::Matrix(expr, sparse = TRUE), annotation = ann)
}

# Create control condition
ctrl <- create_expr(400)

# Create treatment condition with altered expression
# Simulate upregulation of some genes in treatment
treatment_fc <- setNames(rep(1.5, 10), ligands[1:10])
treat <- create_expr(400, seed_offset = 100, fold_changes = treatment_fc)

Running Individual Analyses

# Analyze control
ctrl_result <- ExtractEdges(
  expression = ctrl$expr,
  annotation = ctrl$annotation,
  species = "human",
  database = "lrc2p",
  min_pct = 0.05
)

# Analyze treatment
treat_result <- ExtractEdges(
  expression = treat$expr,
  annotation = treat$annotation,
  species = "human",
  database = "lrc2p",
  min_pct = 0.05
)

cat("Control edges:", nrow(ctrl_result$edges), "\n")
cat("Treatment edges:", nrow(treat_result$edges), "\n")

Differential Analysis

Running DiffEdges

# Compare treatment vs control
diff_result <- DiffEdges(
  reference = ctrl_result,
  target = treat_result,
  log2fc_threshold = 0.5,
  pseudo_count = 0.001
)

print(diff_result)

Understanding the Output

# Summary of differential analysis
summary(diff_result)

# View changed edges
head(diff_result$changed_edges)

Visualization

Differential Heatmap

PlotDiffHeatmap(diff_result, metric = "log2fc")

Volcano Plot

PlotVolcano(diff_result, 
            log2fc_threshold = 0.5,
            label_top = 10,
            title = "Treatment vs Control")

Summarization Functions

By Cluster Pair

# Summarize changes by cluster pairs
cluster_summary <- SummarizeByCluster(diff_result)
head(cluster_summary)

By LR Pair

# Summarize changes by ligand-receptor pairs
lr_summary <- SummarizeByLR(diff_result)
head(lr_summary)

Filtering Significant Changes

# Get strongly upregulated edges
up_edges <- diff_result$changed_edges[log2fc > 1]
cat("Upregulated edges:", nrow(up_edges), "\n")

# Get strongly downregulated edges
down_edges <- diff_result$changed_edges[log2fc < -1]
cat("Downregulated edges:", nrow(down_edges), "\n")

Interpreting Results

Key Metrics

Metric Description Interpretation
log2fc Log2 fold change Positive = increased in treatment
delta_mean Difference in expression weight Magnitude of change
delta_specificity Difference in specificity Change in selectivity

Biological Interpretation

  1. High log2fc + High specificity: Strong, specific upregulation
  2. High log2fc + Low specificity: Widespread upregulation
  3. Negative log2fc: Decreased communication
  4. Near-zero log2fc: Stable communication

Exporting Results

# Export to Excel
ExportDiff(diff_result, "differential_analysis.xlsx")

# Export to CSV
write.csv(diff_result$changed_edges, "changed_edges.csv", row.names = FALSE)

Best Practices

1. Sample Matching

Ensure comparable cell populations between conditions:

# Check cluster proportions
table(ctrl_result$annotation$cluster)
table(treat_result$annotation$cluster)

2. Multiple Testing Consideration

For large-scale analyses, consider adjusting for multiple testing:

# Example: Bonferroni correction
n_tests <- nrow(diff_result$changed_edges)
alpha_adjusted <- 0.05 / n_tests

3. Biological Validation

Prioritize edges for validation based on: - High absolute log2fc - High specificity scores - Known biological relevance

Session Info

sessionInfo()
#> R version 4.6.0 (2026-04-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 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.26.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] ggplot2_4.0.3     data.table_1.18.4 NOVA_1.0.0        rmarkdown_2.31   
#> 
#> loaded via a namespace (and not attached):
#>  [1] Matrix_1.7-5       gtable_0.3.6       jsonlite_2.0.0     dplyr_1.2.1       
#>  [5] compiler_4.6.0     tidyselect_1.2.1   Rcpp_1.1.1-1.1     parallel_4.6.0    
#>  [9] jquerylib_0.1.4    scales_1.4.0       yaml_2.3.12        fastmap_1.2.0     
#> [13] lattice_0.22-9     R6_2.6.1           generics_0.1.4     knitr_1.51        
#> [17] tibble_3.3.1       maketools_1.3.2    bslib_0.11.0       pillar_1.11.1     
#> [21] RColorBrewer_1.1-3 rlang_1.2.0        cachem_1.1.0       xfun_0.57         
#> [25] sass_0.4.10        sys_3.4.3          S7_0.2.2           otel_0.2.0        
#> [29] cli_3.6.6          withr_3.0.2        magrittr_2.0.5     digest_0.6.39     
#> [33] grid_4.6.0         lifecycle_1.0.5    vctrs_0.7.3        evaluate_1.0.5    
#> [37] glue_1.8.1         farver_2.1.2       buildtools_1.0.0   tools_4.6.0       
#> [41] pkgconfig_2.0.3    htmltools_0.5.9

Author

Zaoqu Liu