Skip to contents

Motivation

Seurat’s plotting functions are designed for exploration. Getting them to publication quality typically requires layering ggplot2 theme adjustments, adding cell borders, repositioning legends, and reformatting axis labels — the same boilerplate repeated across every figure in a manuscript.

BadranSeq wraps this into a small set of functions with opinionated defaults: cell borders on, cluster labels on, clean white theme, legend at bottom. The goal is a plot that looks good in a paper without additional customisation.

From Seurat defaults to publication-ready

The quickest way to see the difference — the same data plotted with Seurat::DimPlot() and BadranSeq::do_UmapPlot():

p1 <- Seurat::DimPlot(pbmc3k, reduction = "umap") +
  ggtitle("Seurat::DimPlot()")

p2 <- do_UmapPlot(pbmc3k) +
  ggtitle("BadranSeq::do_UmapPlot()")

p1 + p2

BadranSeq adds cell borders, boxed cluster labels, and a clean theme automatically. No theme() calls needed.

UMAP plots

do_UmapPlot() is the workhorse for UMAP visualisation.

Basic usage

do_UmapPlot(pbmc3k)

Group by a metadata column

do_UmapPlot(pbmc3k, group.by = "seurat_annotations")

Group by multiple metadata columns

Pass a vector of column names to create side-by-side panels:

do_UmapPlot(pbmc3k, group.by = c("seurat_clusters", "seurat_annotations"))

Control panel layout

When combining multiple panels, use ncol and nrow to control the patchwork layout. For example, stack the panels vertically with ncol = 1:

do_UmapPlot(pbmc3k,
            group.by = c("seurat_clusters", "seurat_annotations"),
            ncol = 1)

Hide the legend

do_UmapPlot(pbmc3k, show.legend = FALSE)

Adjust point size and remove labels

do_UmapPlot(pbmc3k, pt.size = 0.3, label = FALSE)

Remove cell borders

do_UmapPlot(pbmc3k, plot_cell_borders = FALSE)

PCA with automatic variance labels

do_PcaPlot() does what Seurat’s PCA plot does not — it calculates variance explained per PC and formats the axis labels as “PC1 (X.X%)”.

p1 <- Seurat::DimPlot(pbmc3k, reduction = "pca") +
  ggtitle("Seurat::DimPlot(reduction = 'pca')")

p2 <- do_PcaPlot(pbmc3k) +
  ggtitle("BadranSeq::do_PcaPlot()")

p1 + p2

The variance annotation is automatic — no manual calculation or label formatting required.

Different dimension pairs

Explore PC2 vs PC3:

do_PcaPlot(pbmc3k, dims = c(2, 3))

The unified entry point

do_DimPlot() routes to the appropriate handler based on the reduction argument:

reduction Routes to Axis labels
"umap" do_UmapPlot() UMAP1, UMAP2
"pca" do_PcaPlot() PC1 (X.X%), PC2 (X.X%)
other .do_DimPlot_internal() REDUCTION1, REDUCTION2
p1 <- do_DimPlot(pbmc3k, reduction = "umap") + ggtitle("reduction = 'umap'")
p2 <- do_DimPlot(pbmc3k, reduction = "pca") + ggtitle("reduction = 'pca'")

p1 + p2

Use do_DimPlot() when writing general-purpose code that should work with any reduction. Use the specific functions (do_UmapPlot(), do_PcaPlot()) when you know the reduction type.

Feature expression plots

do_FeaturePlot() overlays gene expression on the embedding using a viridis colour scale.

do_FeaturePlot(pbmc3k, features = c("CD3D", "CD14", "MS4A1"))

Cells are ordered by expression by default (order = TRUE), so high-expressing cells are plotted on top — avoiding the common problem of rare populations being hidden beneath low-expressing cells.

Control panel layout

Use ncol and nrow to arrange multi-feature panels. For example, stack them vertically:

do_FeaturePlot(pbmc3k,
               features = c("CD3D", "CD14", "MS4A1"),
               ncol = 1)

Split-panel silhouettes

This is BadranSeq’s signature visualisation feature. When you pass split.by, each panel shows:

  1. A grey silhouette of all cells (spatial context)
  2. Black borders on all cells
  3. Coloured cells only for the current split category

This preserves the spatial layout across panels, making it easy to see where a subpopulation sits relative to the full dataset.

First, add a synthetic condition to demonstrate:

set.seed(42)
pbmc3k$condition <- sample(
  c("Control", "Treatment"),
  ncol(pbmc3k),
  replace = TRUE
)

Now compare Seurat’s default split with BadranSeq’s silhouette approach:

p1 <- Seurat::DimPlot(pbmc3k, reduction = "umap", split.by = "condition") +
  ggtitle("Seurat::DimPlot(split.by)")

p1

do_UmapPlot(pbmc3k, split.by = "condition")

In the Seurat version, each panel only shows its own cells — you lose the sense of where those cells sit in the overall structure. In BadranSeq’s version, the grey background preserves this context: you can immediately see which clusters are enriched in each condition.

The silhouette approach works with any grouping:

do_UmapPlot(pbmc3k, group.by = "seurat_annotations", split.by = "condition")

Violin plots

BadranSeq provides two violin plot functions built on fetch_feature_data():

The primary function is do_StatsViolinPlot(). If you want the same aesthetic without statistics, use do_ViolinPlot() or set pairwise.display = "none" in do_StatsViolinPlot().

Statistical violin plots

do_StatsViolinPlot() runs a nonparametric omnibus test (Kruskal–Wallis) and pairwise comparisons (Dunn’s test) by default, annotating the plot with p-values:

do_StatsViolinPlot(pbmc3k, features = "CD3D",
                   group.by = "seurat_annotations",
                   pairwise.display = "none")
## Warning:  Number of labels is greater than default palette color count.
##  Select another color `palette` (and/or `package`).
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.

With nine annotation groups, pairwise brackets would overwhelm the figure. The pairwise.display argument controls this:

Controlling pairwise comparisons

Value Behaviour
"significant" Only significant pairs (default)
"non-significant" Only non-significant pairs
"all" Every pairwise comparison
"none" Omnibus test only, no brackets

Setting pairwise.display = "none" keeps the subtitle showing the overall test statistic while removing all bracket annotations — useful for publication figures where the brackets would overlap.

Comparing specific groups with group.levels

Often you do not want to compare all cell types — perhaps only a few are biologically relevant. The group.levels argument filters the grouping variable to a specified subset before the statistical test is run, so the test is performed only on the groups you care about.

With fewer groups, pairwise brackets become readable again:

do_StatsViolinPlot(pbmc3k, features = "CD3D",
                   group.by = "seurat_annotations",
                   group.levels = c("Naive CD4 T", "Memory CD4 T", "CD8 T"))
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.

This is cleaner than subsetting the Seurat object manually and avoids the need to drop unused factor levels.

Parametric and Bayesian alternatives

The default nonparametric test is appropriate for most scRNA-seq data, but you can switch to parametric (ANOVA / t-test), robust, or Bayesian tests via the type argument:

do_StatsViolinPlot(pbmc3k, features = "CD3D",
                   group.by = "seurat_annotations",
                   group.levels = c("Naive CD4 T", "Memory CD4 T", "CD8 T"),
                   type = "parametric")
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.

Multiple genes

Pass a vector of gene names to get a combined patchwork layout:

do_StatsViolinPlot(pbmc3k,
                   features = c("CD3D", "CD8A"),
                   group.by = "seurat_annotations",
                   pairwise.display = "none",
                   ncol = 2)
## Warning:  Number of labels is greater than default palette color count.
##  Select another color `palette` (and/or `package`).
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.
## Warning:  Number of labels is greater than default palette color count.
##  Select another color `palette` (and/or `package`).
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.

Plain violin plots

do_ViolinPlot() produces a violin + boxplot + jitter + centrality marker (median diamond) without any statistical annotation. Use this when you want a clean descriptive plot:

do_ViolinPlot(pbmc3k, features = "CD3D",
              group.by = "seurat_annotations")

Sankey / alluvial diagrams

do_SankeyPlot() visualises relationships between categorical metadata variables using an alluvial (Sankey) diagram. Each stratum is labelled and coloured; flows connect cells across variables.

Clusters to cell types

A common use case — mapping cluster IDs to biological annotations:

do_SankeyPlot(pbmc3k,
              columns = c("seurat_clusters", "seurat_annotations"))
## 62 cell(s) with NA values excluded from plot.

This immediately shows which clusters map cleanly to a single cell type and which are split across multiple annotations.