| name | cellchat |
| description | Cell-cell communication analysis with CellChat (R). Covers (1) the standard single-dataset pipeline — database setup, communication inference, pathway-level aggregation, centrality, communication-pattern discovery, similarity-based clustering; (2) multi-dataset comparison — differential interactions, altered pathways, dysregulated ligand-receptor pairs; (3) the lifting workflow for comparing datasets with different cellular compositions. Works on Seurat objects, SingleCellExperiment, or counts+metadata. |
| license | GPL-3.0-or-later |
| metadata | null |
CellChat: Cell-Cell Communication Inference
Overview
CellChat infers, visualizes, and compares intercellular communication networks from single-cell and spatial transcriptomics. Each communication event is a ligand–receptor (L-R) interaction between two cell groups; CellChat aggregates L-R pairs into signaling pathways and computes per-pair / per-pathway communication probabilities. Two object-level slots carry the inferred network: @net (L-R level) and @netP (pathway level).
Built-in databases (CellChatDB) cover human and mouse with hundreds of curated L-R pairs organized by signaling category: Secreted Signaling (e.g. cytokines, chemokines), ECM-Receptor, and Cell-Cell Contact. You can subset to one category for a focused analysis.
When to Use This Skill
- Inferring cell-cell signaling between annotated cell types from one scRNA-seq / snRNA-seq / spatial dataset
- Comparing communication between two conditions (disease vs control, treated vs untreated, two timepoints)
- Comparing communication between datasets with different cell compositions (e.g. embryonic E13 vs E14 with non-overlapping cell types — uses
liftCellChat)
- Identifying dysregulated L-R pairs between conditions
- Discovering communication patterns (NMF-based outgoing / incoming pattern modules)
- Functional / structural similarity clustering of pathways
Not for: gene-level differential expression (use Seurat / DESeq2), gene-set enrichment of communication (use hdwgcna + Enrichr), trajectory analysis (use scVelo / Slingshot).
Prerequisites
- R ≥ 4.1
- A Seurat object (v4 or v5) with annotated cell types OR a counts matrix + cell-level metadata data frame
- Recommend ≥ 50 cells per cell type for stable inference
install.packages("BiocManager")
BiocManager::install(c("ComplexHeatmap", "NMF", "circlize"))
devtools::install_github("jinworks/CellChat")
Optional Python dep for embedding plots: pip install umap-learn.
Pipeline 1 — Single-Dataset Analysis
The standard pipeline takes a Seurat-like object with cell-type labels and returns an annotated CellChat object with inferred communication at L-R and pathway levels.
library(CellChat)
library(patchwork)
options(stringsAsFactors = FALSE)
future::plan("multisession", workers = 4)
data.input <- GetAssayData(seurat_obj, layer = "data", assay = "RNA")
meta <- seurat_obj@meta.data
cellchat <- createCellChat(object = data.input, meta = meta, group.by = "cell_type")
CellChatDB <- CellChatDB.human
showDatabaseCategory(CellChatDB)
CellChatDB.use <- subsetDB(CellChatDB, search key
cellchatDB CellChatDB.use
cellchat subsetDatacellchat
cellchat identifyOverExpressedGenescellchat
cellchat identifyOverExpressedInteractionscellchat
cellchat computeCommunProbcellchat type
cellchat filterCommunicationcellchat min.cells
cellchat computeCommunProbPathwaycellchat
cellchat aggregateNetcellchat
cellchat netAnalysis_computeCentralitycellchat slot.name
saveRDScellchat
The result object now contains:
| Slot | What |
|---|
cellchat@net$prob | L-R-level communication probability (group × group × LRpair) |
cellchat@net$pval | Permutation p-values |
cellchat@net$count / cellchat@net$weight | Aggregated cell-cell matrices |
cellchat@netP$prob | Pathway-level communication probability (group × group × pathway) |
cellchat@netP$centr | Per-pathway centrality scores |
cellchat@idents | Cell-group labels (used by all visualizations) |
Extract communications
df.net <- subsetCommunication(cellchat)
df.path <- subsetCommunication(cellchat, slot.name = "netP")
df.sub <- subsetCommunication(cellchat, signaling = c("CXCL", "TGFb"))
df.dir <- subsetCommunication(cellchat, sources.use = c(1,2), targets.use = c(4,5))
Visualize one pathway
pathways.show <- c("CXCL")
netVisual_aggregate(cellchat, signaling = pathways.show, layout = "circle")
netVisual_aggregate(cellchat, signaling = pathways.show, layout = "chord")
netVisual_heatmap (cellchat, signaling = pathways.show, color.heatmap = "Reds")
vertex.receiver <- seq(1, 4)
netVisual_aggregate(cellchat, signaling = pathways.show, vertex.receiver = vertex.receiver)
netAnalysis_contribution(cellchat, signaling = pathways.show)
pairLR.CXCL <- extractEnrichedLR(cellchat, signaling = geneLR.return
LR.show pairLR.CXCL
netVisual_individualcellchat signaling pathways.show pairLR.use LR.show layout
Centrality — which cell type drives each signal?
netAnalysis_signalingRole_network(cellchat, signaling = "CXCL",
width = 8, height = 2.5, font.size = 10)
gg1 <- netAnalysis_signalingRole_scatter(cellchat)
ht1 <- netAnalysis_signalingRole_heatmap(cellchat, pattern = "outgoing")
ht2 <- netAnalysis_signalingRole_heatmap(cellchat, pattern = "incoming")
ht1 + ht2
Communication pattern discovery (NMF)
CellChat factorizes the network into "patterns" — modules of cell groups that share outgoing or incoming signaling profiles.
library(NMF); library(ggalluvial)
selectK(cellchat, pattern = "outgoing")
cellchat <- identifyCommunicationPatterns(cellchat, pattern = "outgoing", k = 4)
netAnalysis_river(cellchat, pattern = "outgoing")
netAnalysis_dot (cellchat, pattern = "outgoing")
selectK(cellchat, pattern = "incoming")
cellchat <- identifyCommunicationPatterns(cellchat, pattern = "incoming", k = 3)
netAnalysis_river(cellchat, pattern = "incoming")
Choose k from where the cophenetic / Frobenius / silhouette curves elbow.
Pathway similarity & clustering
cellchat <- computeNetSimilarity(cellchat, type = "functional")
cellchat <- netEmbedding(cellchat, type = "functional")
cellchat <- netClustering(cellchat, type = "functional")
netVisual_embedding(cellchat, type = "functional", label.size = 3.5)
cellchat <- computeNetSimilarity(cellchat, type = "structural")
cellchat <- netEmbedding(cellchat, type = "structural")
cellchat <- netClustering(cellchat, type = "structural")
netVisual_embedding(cellchat, type = "structural", label.size =
Convenience: Rscript scripts/build_cellchat.R --rds seurat.rds --group-by cell_type --species human --signaling 'Secreted Signaling' --out cellchat.rds. See references/single_dataset.md for parameter tuning, slot-by-slot output, and DB customization.
Source: CellChat-vignette.html.
Pipeline 2 — Multi-Dataset Comparison
Standard comparison: two conditions (NL vs LS, AD vs Ctrl, treated vs untreated), same cell types in both. Run Pipeline 1 once per condition, then merge.
cellchat.NL <- readRDS("results/cellchat_NL.rds")
cellchat.LS <- readRDS("results/cellchat_LS.rds")
object.list <- list(NL = cellchat.NL, LS = cellchat.LS)
cellchat <- mergeCellChat(object.list, add.names = names(object.list))
1. Overall comparison
gg1 <- compareInteractions(cellchat, show.legend = FALSE, group = c(1, 2))
gg2 <- compareInteractions(cellchat, show.legend = FALSE, group = c(1, 2), measure = "weight")
gg1 + gg2
2. Differential cell-cell network
netVisual_diffInteraction(cellchat, weight.scale = TRUE)
netVisual_diffInteraction(cellchat, weight.scale = TRUE, measure = "weight")
gg1 <- netVisual_heatmap(cellchat)
gg2 <- netVisual_heatmap(cellchat, measure = "weight")
gg1 + gg2
3. Altered signaling pathways
gg1 <- rankNet(cellchat, mode = "comparison", measure = "weight",
stacked = TRUE, do.stat = TRUE)
gg2 <- rankNet(cellchat, mode = "comparison", measure = "weight",
stacked = FALSE, do.stat = TRUE)
gg1 + gg2
4. Dysregulated L-R pairs
This is the hardest-to-find but most actionable analysis. For each L-R pair, test whether the underlying ligand / receptor genes are differentially expressed across conditions, then keep only the pairs with significant changes.
pos.dataset <- "LS"
features.name <- paste0(pos.dataset, ".merged")
cellchat <- identifyOverExpressedGenes(
cellchat, group.dataset = "datasets",
pos.dataset = pos.dataset,
features.name = features.name,
only.pos = FALSE,
thresh.pc = 0.1, thresh.fc = 0.05, thresh.p = 0.05,
group.DE.combined = FALSE
)
net <- netMappingDEG(cellchat, features.name = features.name, variable.all = TRUE)
net.up <- subsetCommunication(cellchat, net = net, datasets
ligand.logFC receptor.logFC
net.down subsetCommunicationcellchat net net datasets
ligand.logFC receptor.logFC
pairLR.use.up net.up drop
netVisual_bubblecellchat
pairLR.use pairLR.use.up
sources.use targets.use
comparison
angle.x
remove.isolate
title.name
computeEnrichmentScorenet.up species variable.both
computeEnrichmentScorenet.down species variable.both
5. Pathway-level manifold (when conditions look very different)
cellchat <- computeNetSimilarityPairwise(cellchat, type = "functional")
cellchat <- netEmbedding(cellchat, type = "functional")
cellchat <- netClustering(cellchat, type = "functional")
netVisual_embeddingPairwise(cellchat, type = "functional", label.size = 3.5)
rankSimilarity(cellchat, type = "functional")
Conserved pathways cluster together across conditions; context-specific ones spread apart.
Convenience: Rscript scripts/compare_cellchat.R --condition-a cellchat_NL.rds --condition-b cellchat_LS.rds --out merged.rds.
Source: Comparison_analysis_of_multiple_datasets.html.
Pipeline 3 — Comparison with Different Cellular Compositions
When two datasets have different cell types (e.g. E13 has neuroblasts that E14 doesn't, or one disease tissue has tumor cells absent from the control), you can't merge directly — the network matrices have different dimensions. Solution: lift each object to a shared cell-type universe.
cellchat.E13 <- readRDS("results/cellchat_E13.rds")
cellchat.E14 <- readRDS("results/cellchat_E14.rds")
group.new <- levels(cellchat.E14@idents)
cellchat.E13 <- liftCellChat(cellchat.E13, group.new)
object.list <- list(E13 = cellchat.E13, E14 = cellchat.E14)
cellchat <- mergeCellChat(object.list, add.names = names(object.list),
cell.prefix = TRUE)
compareInteractions(cellchat, group = c
netVisual_diffInteractioncellchat weight.scale
rankNetcellchat mode stacked do.stat
netVisual_bubblecellchat sources.use targets.use
comparison angle.x
What lifting does (and doesn't):
- Updates
@net, @netP, @idents to use the new cell-type list.
- Leaves expression data (
@data.input, @data.signaling) alone — these still represent only the cells the original object contained.
- Cell types only in
group.new (not in the original object) get zero entries in the network matrices — they participate as nodes but never as senders/receivers.
When to use vs. not:
- Use lifting when cellular composition differs and you want a fair side-by-side visualization at the same set of nodes.
- Don't use lifting when you're testing "did communication X disappear in disease?" if disease cells genuinely don't exist in control — that's a meaningful biological difference, and lifting hides it.
Source: Comparison_analysis_of_multiple_datasets_with_different_cellular_compositions.html.
Visualization Cookbook
A short tour. Full patterns in references/plotting_guide.md.
Multi-pathway bubble plot
netVisual_bubble(cellchat, sources.use = 4, targets.use = 5:11,
remove.isolate = FALSE)
netVisual_bubble(cellchat, sources.use = 4, targets.use = 5:11,
signaling = c("CCL", "CXCL"))
pairLR.use <- extractEnrichedLR(cellchat, signaling = c("CCL", "CXCL", "FGF"))
netVisual_bubble(cellchat, pairLR.use = pairLR.use,
sources.use = c(3 targets.use
Chord at the L-R level
netVisual_chord_gene(cellchat, sources.use = 4, targets.use = 5:11,
lab.cex = 0.5, legend.pos.y = 30)
group.cellType <- c(rep("FIB", 4), rep("DC", 4), rep("TC", 4))
names(group.cellType) <- levels(cellchat@idents)
netVisual_chord_cell(cellchat, signaling = "CXCL", group = group.cellType,
title.name
Gene expression of a pathway
plotGeneExpression(cellchat, signaling = "CXCL", enriched.only = TRUE, type = "violin")
plotGeneExpression(cellchat, signaling = "CXCL", split.by = "datasets",
colors.ggplot = TRUE, type = "violin")
Key Parameters to Adjust
createCellChat / group.by
- Cell-type column should be a meaningful, stable annotation — running CellChat on coarse
cell_type is far more interpretable than on fine leiden_cluster.
subsetDB(search = ...)
"Secreted Signaling" — cytokines, chemokines, growth factors (most common)
"ECM-Receptor" — integrins, matrix
"Cell-Cell Contact" — notch, ephrin
- Omit
subsetDB to use all three (slower but more comprehensive)
computeCommunProb
type = "triMean" (default): robust to outliers
type = "truncatedMean" + trim = 0.1: more stringent
population.size = TRUE: down-weights small populations (use when groups vary 10×+ in size)
filterCommunication(min.cells = 10)
- For HPC datasets with 100k+ cells, bump to 50-100 — single-cell-resolution false positives are an issue.
identifyCommunicationPatterns(k = ...)
- Pick
k from selectK() curves. Don't go higher than where the cophenetic stops dropping.
Best Practices
- Always use log-normalized counts, not raw counts — pass
GetAssayData(seurat_obj, layer = "data").
- Run identifyOverExpressedInteractions before
computeCommunProb — skipping it pushes CellChat to consider every L-R pair globally and inflates false positives.
- Save the per-condition object before merging.
mergeCellChat doesn't preserve everything — netAnalysis_computeCentrality results are accessible only via the per-condition object.
netAnalysis_computeCentrality must be run on @netP (slot.name = "netP"). Running it on @net (L-R level) is hours slower and rarely useful.
- Permutation p-values are conservative — a "non-significant" L-R may still be biologically real if you have low cell counts per group. Inspect
@net$prob values directly when borderline.
- For multi-dataset comparison, run all preprocessing identically across conditions (same
subsetDB filter, same min.cells, same type). Mismatched parameters create artifactual "differences."
- For lifting, build the per-condition objects on the union gene set first — otherwise a gene present in dataset A but missing from dataset B will silently kill L-R pairs after the lift.
End-to-End Template
assets/cellchat_template.R is a single parameterized script — edit the CONFIGURATION block and run end-to-end through all three pipelines.
Convenience Scripts
scripts/build_cellchat.R — single-dataset analysis (Pipeline 1).
scripts/compare_cellchat.R — multi-dataset merging + comparison (Pipeline 2); supports --lift for Pipeline 3.
References