Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Infer cell-cell communication networks from scRNA-seq data using CellChat, NicheNet, and LIANA for ligand-receptor interaction analysis. Use when inferring ligand-receptor interactions between cell types.
.claude/skills/bio-single-cell-cell-communication/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-17 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-11 | ✗→✓ | ▲ Improved | — | — |
Reference examples tested with: ggplot2 3.5+, scanpy 1.10+
Before using code patterns, verify installed versions match. If versions differ:
pip show <package> then help(module.function) to check signaturespackageVersion('<pkg>') then ?function_name to verify parametersIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
"Infer cell-cell communication from my scRNA-seq data" → Predict ligand-receptor interactions between cell types and visualize intercellular signaling networks.
CellChat::createCellChat() → computeCommunProb() → netAnalysis()liana.method.cellchat() (LIANA framework)Goal: Infer and quantify intercellular communication networks from scRNA-seq data using curated ligand-receptor databases.
Approach: Create a CellChat object from a Seurat object with cell type labels, select a signaling database subset, identify overexpressed ligands/receptors, compute communication probabilities using the trimean method, then aggregate into pathway-level networks.
rlibrary(CellChat) library(Seurat) # Create CellChat object from Seurat cellchat <- createCellChat(object = seurat_obj, group.by = 'cell_type') # Set ligand-receptor database CellChatDB <- CellChatDB.human # or CellChatDB.mouse cellchat@DB <- CellChatDB # Subset to secreted signaling (optional) CellChatDB.use <- subsetDB(CellChatDB, search = 'Secreted Signaling') cellchat@DB <- CellChatDB.use # Preprocessing cellchat <- subsetData(cellchat) cellchat <- identifyOverExpressedGenes(cellchat) cellchat <- identifyOverExpressedInteractions(cellchat) # Compute communication probability cellchat <- computeCommunProb(cellchat, type = 'triMean') cellchat <- filterCommunication(cellchat, min.cells = 10) # Infer signaling pathways cellchat <- computeCommunProbPathway(cellchat) cellchat <- aggregateNet(cellchat)
r# Network plots netVisual_circle(cellchat@net$count, vertex.weight = groupSize, weight.scale = TRUE, label.edge = FALSE, title.name = 'Number of interactions') netVisual_circle(cellchat@net$weight, vertex.weight = groupSize, weight.scale = TRUE, label.edge = FALSE, title.name = 'Interaction strength') # Heatmap of interactions netVisual_heatmap(cellchat, color.heatmap = 'Reds') # Specific pathway visualization netVisual_aggregate(cellchat, signaling = 'WNT', layout = 'circle') netVisual_aggregate(cellchat, signaling = 'WNT', layout = 'chord') # Bubble plot netVisual_bubble(cellchat, sources.use = c(1, 2), targets.use = c(3, 4), remove.isolate = FALSE) # Chord diagram for ligand-receptor pairs netVisual_chord_gene(cellchat, sources.use = 1, targets.use = c(2, 3, 4), lab.cex = 0.5, legend.pos.x = 10)
r# Identify signaling roles cellchat <- netAnalysis_computeCentrality(cellchat, slot.name = 'netP') # Signaling role heatmap netAnalysis_signalingRole_heatmap(cellchat, signaling = c('WNT', 'TGFb', 'BMP')) # Dominant senders/receivers netAnalysis_signalingRole_scatter(cellchat) # Compare pathways rankNet(cellchat, mode = 'comparison', stacked = TRUE, do.stat = TRUE)
r# Create separate CellChat objects cellchat_ctrl <- createCellChat(subset(seurat_obj, condition == 'control'), group.by = 'cell_type') cellchat_treat <- createCellChat(subset(seurat_obj, condition == 'treatment'), group.by = 'cell_type') # Process both (same steps as above) # ... # Merge for comparison cellchat_list <- list(Control = cellchat_ctrl, Treatment = cellchat_treat) cellchat_merged <- mergeCellChat(cellchat_list, add.names = names(cellchat_list)) # Compare interactions compareInteractions(cellchat_merged, show.legend = FALSE) # Differential interactions netVisual_diffInteraction(cellchat_merged, weight.scale = TRUE) netVisual_heatmap(cellchat_merged) # Pathway comparison rankNet(cellchat_merged, mode = 'comparison', stacked = TRUE)
rlibrary(nichenetr) library(Seurat) library(tidyverse) # Load NicheNet databases ligand_target_matrix <- readRDS('ligand_target_matrix.rds') lr_network <- readRDS('lr_network.rds') weighted_networks <- readRDS('weighted_networks.rds') # Define sender and receiver cells sender_celltypes <- c('Macrophage', 'Dendritic') receiver <- 'T_cell' # Get expressed genes expressed_genes_sender <- get_expressed_genes(sender_celltypes, seurat_obj, pct = 0.10) expressed_genes_receiver <- get_expressed_genes(receiver, seurat_obj, pct = 0.10) # Define gene set of interest (e.g., DE genes in receiver) geneset_oi <- FindMarkers(seurat_obj, ident.1 = 'activated_T', ident.2 = 'naive_T') %>% filter(p_val_adj < 0.05, avg_log2FC > 0.5) %>% rownames() # Background genes background_genes <- expressed_genes_receiver # Define potential ligands ligands <- lr_network %>% pull(from) %>% unique() expressed_ligands <- intersect(ligands, expressed_genes_sender) receptors <- lr_network %>% pull(to) %>% unique() expressed_receptors <- intersect(receptors, expressed_genes_receiver) potential_ligands <- lr_network %>% filter(from %in% expressed_ligands & to %in% expressed_receptors) %>% pull(from) %>% unique() # NicheNet ligand activity analysis ligand_activities <- predict_ligand_activities( geneset = geneset_oi, background_expressed_genes = background_genes, ligand_target_matrix = ligand_target_matrix, potential_ligands = potential_ligands ) # Top ligands best_ligands <- ligand_activities %>% top_n(20, pearson) %>% arrange(-pearson) %>% pull(test_ligand)
r# Ligand-target heatmap active_ligand_target_links <- best_ligands %>% lapply(get_weighted_ligand_target_links, geneset_oi, ligand_target_matrix, n = 200) %>% bind_rows() %>% drop_na() vis_ligand_target <- prepare_ligand_target_visualization( ligand_target_df = active_ligand_target_links, ligand_target_matrix = ligand_target_matrix, cutoff = 0.33 ) p_ligand_target <- vis_ligand_target %>% make_heatmap_ggplot('Prioritized ligands', 'Target genes', color = 'purple', legend_position = 'top') # Ligand-receptor heatmap lr_network_top <- lr_network %>% filter(from %in% best_ligands & to %in% expressed_receptors) %>% distinct(from, to) vis_ligand_receptor <- get_exprs_avg(seurat_obj, 'cell_type') %>% inner_join(lr_network_top, by = c('gene' = 'to')) p_ligand_receptor <- vis_ligand_receptor %>% make_heatmap_ggplot('Ligands', 'Receptors', color = 'mediumvioletred') # Ligand expression by cell type p_ligand_expression <- DotPlot(seurat_obj, features = best_ligands, cols = 'RdYlBu') + RotatedAxis()
pythonimport liana as li import scanpy as sc adata = sc.read_h5ad('adata.h5ad') # Run LIANA with multiple methods li.mt.rank_aggregate(adata, groupby='cell_type', resource_name='consensus', expr_prop=0.1, verbose=True) # Get results liana_results = adata.uns['liana_res'] # Filter significant interactions sig_interactions = liana_results[liana_results['liana_rank'] < 0.01] # Visualize li.pl.dotplot(adata, colour='magnitude_rank', size='specificity_rank', source_groups=['Macrophage'], target_groups=['T_cell'])
python# Multi-sample/condition analysis li.mt.rank_aggregate(adata, groupby='cell_type', resource_name='consensus', use_raw=False, verbose=True) # Build tensor for decomposition li.multi.build_tensor(adata, sample_key='sample', groupby='cell_type', ligand_key='ligand_complex', receptor_key='receptor_complex') # Run tensor decomposition li.multi.decompose_tensor(adata, n_components=5) # Visualize factor loadings li.pl.factor_loadings(adata, factor_idx=0)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.