Skip to content

Best practices for filtering and visualizing differential interactions in CellChat #448

@OryLies

Description

@OryLies

Hi,

I’ve been following the tutorial “Comparison analysis of multiple datasets using CellChat” to compare two conditions. While the workflow runs fine, I find the default bubble plots difficult to interpret, especially since the same ligand–receptor pairs sometimes appear in both up- and downregulated pathway categories.

To obtain a clearer directional interpretation, I implemented the following approach:

  1. Extracts communication probabilities from each condition.
  2. Computes Δ probability (Condition2 − Condition1) for each ligand–receptor pair.
  3. Filters interactions that are either significant in at least one condition (pval.Condition1 < 0.05 | pval.Condition2 < 0.05).
  4. Further restricts to interactions where the ligand or receptor is a DEG between conditions (ligand.pvalues < 0.05 | receptor.pvalues < 0.05).
  5. Visualizes Δ probability with dot plots (color = direction, size = magnitude).

Would this be an appropriate way to analyze and visualize differential communication from CellChat outputs, or would you recommend a different strategy? I’d also like to ask specifically whether the thresholds I chose for communication p-values and DEGs are appropriate, or if you would recommend alternative settings or additional filters to better capture meaningful differential interactions.

Thanks in advance for your guidance!

cellchat.Condition1 <- readRDS('cellchat_Condition1_Processed.rds')
cellchat.Condition2 <- readRDS('cellchat_Condition2_Processed.rds')
object.list <- list(Condition1 = cellchat.Condition1, Condition2 = cellchat.Condition2)
cellchat` <- mergeCellChat(object.list, add.names = names(object.list),cell.prefix = TRUE)
pos.dataset = "Condition2"
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 = "Condition1", ligand.logFC = 0.05, receptor.logFC = NULL)
net.down <- subsetCommunication(cellchat, net = net, datasets = "Condition2", ligand.logFC = -0.05, receptor.logFC = NULL)
cells.use <- c("CellType1", "CellType2", "CellType3", ...)
pathways.interest <- c("Pathway1", "Pathway2", "Pathway3", ...)

df_Condition1 <- subsetCommunication(cellchat.Condition1,
                                     sources.use = cells.use,
                                     targets.use = cells.use,
                                     signaling = pathways.interest)

df_Condition2 <- subsetCommunication(cellchat.Condition2,
                                     sources.use = cells.use,
                                     targets.use = cells.use,
                                     signaling = pathways.interest)

df_diff <- df_Condition2 %>%
  rename(prob.Condition2 = prob, pval.Condition2 = pval) %>%
  inner_join(
    df_Condition1 %>%
      rename(prob.Condition1 = prob, pval.Condition1 = pval),
    by = c("source", "target", "interaction_name", "pathway_name")
  ) %>%
  mutate(
    delta_prob = prob.Condition2 - prob.Condition1,
    abs_delta = abs(delta_prob),
    significant_prob = (pval.Condition2 < 0.05 | pval.Condition1 < 0.05)
  ) %>%
  filter(significant_prob, abs_delta > 0.01)

df_pathway <- df_diff %>%
  group_by(pathway_name) %>%
  summarise(mean_delta = mean(delta_prob), .groups = "drop")
# Pathway-level net direction plot
ggplot(df_pathway,
       aes(x = reorder(pathway_name, mean_delta),
           y = mean_delta,
           fill = mean_delta > 0)) +
  geom_col() +
  coord_flip() +
  scale_fill_manual(values = c("#2166AC", "#B2182B"), guide = "none") +
  theme_classic(base_size = 14) +
  labs(title = "Net Direction of Inflammatory Pathways",
       subtitle = "Mean Δ Probability (Condition2 vs Condition1)",
       x = "Pathway",
       y = "Mean Δ Communication")

Image

net.sub <- subsetCommunication(cellchat,
                               net = net,
                               sources.use = cells.use,
                               targets.use = cells.use,
                               signaling = pathways.interest) %>%
  select(source, target, interaction_name,
         ligand.pvalues, receptor.pvalues)

df_diff <- df_diff %>%
  inner_join(net.sub,
             by = c("source", "target", "interaction_name")) %>%
  mutate(DEG_significant =
           (ligand.pvalues < 0.05 | receptor.pvalues < 0.05)) %>%
  filter(DEG_significant)

df_plot <- df_diff %>%
  mutate(direction = ifelse(delta_prob > 0,
                            "Up in Condition2",
                            "Down in Condition2"))
gg_final <- ggplot(df_plot,
                   aes(x = source_target,
                       y = interaction_name_clean)) +
  geom_point(aes(size = abs_delta,
                 fill = direction),
             shape = 21,
             color = "black",
             stroke = 0.4) +
  scale_fill_manual(values = c("Up in Condition2" = "#B2182B",
                               "Down in Condition2" = "#2166AC"),
                    name = "Direction of Δ") +
  scale_size_continuous(range = c(4, 12),
                        name = "Magnitude |Δ Prob|") +
  theme_bw(base_size = 12) +
  theme(
    axis.text.x = element_text(angle = 45,
                               hjust = 1,
                               face = "bold"),
    axis.text.y = element_text(size = 9,
                               face = "italic"),
    panel.grid.major = element_line(color = "grey95"),
    legend.title = element_text(face = "bold")
  ) +
  labs(title = "Differential Cell–Cell Signaling",
       subtitle = paste("Celltypes of Interest Axis | Pathways:",
                        paste(pathways.interest,
                              collapse = ", ")),
       x = "Sender → Receiver",
       y = "Ligand–Receptor Interaction")

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions