Description | Installation | Basic usage | Extended usage | Output explained | Input file format | Downstream
Single-cell RNA sequencing (scRNA-seq) is a well-established technique in the era of next-generation sequencing. From identifying novel cell types, characterising responses to treatment, or mapping cell type-specific eQTLs, its applications are of undeniable value to the field of molecular and cell biology. All these and more rely entirely on the thorough preprocessing of raw scRNA-seq data, for which cell-level quality control is an essential component.
For droplet-based scRNA-seq protocols, βlow qualityβ cells are those that originate from droplets that contain either more than one cell (doublet), no cells (empty), or a damaged cell. While many quality control tools available are designed to identify doublets and empty droplets, few are specialised in identifing damaged cells. Damaged cell detection is more often achieved by setting thresholds for metrics such as average mitochondrial gene expression or UMI and features counts per barcode. These thresholds, even when dynamically calculated, vary across samples, cell types, tissues, treatment conditions, and species. This could result in overly stringent filtering, where many true cells are excluded from downstream but also in overly conservative filtering where many contaminating damaged cells remain. But searching for true damaged droplets in a sample-specific manner is tedious and not always intuitive, leading to user-defined filtering that lacks reproducibility.
limiric
is a quality control package that automates the sample-specific detection of damaged cells in one fast acting and highly reproducible function. It operates on the established biological principle that damaged and healthy cells can be differentiated by the complexity of their mitochondrial and ribosomal gene expression, and that this complexity can be greatly simplified by clustering in lower dimensional space. In addition to predicting damaged cells, there is an option for limiric to estimate and correct for red blood contamination- a rare but significant artefact of certain single cell isolation protocols for which no automatic tool exists.
Built around the community standard Seurat
suite, limiric
was created with the intention of being incorporated seamlessly into a user's pre-existing scRNA-seq analysis workflow. However, the main output of limiric
is a standard, two-column csv that can be used in any platform.
limiric
makes use of the following packages
cowplot
Wilke, 2024. GitHub repodevtools
Wickham and Hester, 2022. GitHub repoDropletQC
Muskovic, 2024. GitHub repodplyr
Wickham et al, 2023. GitHub repoggplot2
Wickham, 2016. GitHub repoMatrix
Bates et al, 2024. Github repopng
Urbanek, 2022. GitHubSeurat
Satija and Farrell et al, 2015. Website, GitHub repoSoupX
Young and Behjati, 2020. GitHub repo
Of these, the following need to be made available in your R
environment before limiric
can be installed. If not already, you can install them together with the following code
packages <- c("cowplot", "devtools", "dplyr", "ggplot2", "Matrix", "png", "Seurat", "SoupX")
for (pkg in packages) {
if (!requireNamespace(pkg, quietly = TRUE)) {
install.packages(pkg)
}
}
Be sure to load these into your environment before continuing with limiric
installation
packages <- c("cowplot", "devtools", "dplyr", "ggplot2", "Matrix", "png", "Seurat", "SoupX")
for (pkg in packages) {
if (!require(pkg, character.only = TRUE)) {
library(pkg)
}
}
Please check the associated documentation if problems occur in the installation of any of the prerequisite packages
After all the prerequisites are installed, you can install the latest development version of limiric
from CRAN using
install.packages("limiric")
Alternatively, the package can be installed from GitHub
using the devtools
package:
devtools::install_github("AlicenJoyHenning/limiric", build_vignettes = TRUE)
To ensure limiric
has correctly installed, run the following to see if you can view the package vignette and the function help page.
library(limiric)
help(package = "limiric")
?limiric()
Additionally, you can perform a test run using a small example dataset stored in the package called test_data
. If you need assistance setting this up, please see Test run for more details.
- Detect damaged cells in a sample using the filtered alignment files (
barcodes.tsv.gz
,features.tsv.gz
,counts.mtx.gz
). All you need to input is your sample name, the directory where your alignment files are stored, and a directory where thelimiric
output can be created.
SRR1234567 <- limiric(
project_name = "SRR1234567",
filtered_path = "/home/user/alignment/SRR1234567/filtered/",
output_path = "/home/user/alignment/limiric/"
)
NB Please keep the following in mind:
- Your files must be zipped, see Input file format for more
- The above usage assumes the sample is of human origin (See parameter:
organism
)- When compiling the data, all genes will be retained (See parameter:
min_cells
)- Red blood cells will automatically be removed from the sample before damaged cell detection (See parameter:
filter_rbc
)- Your output
Seurat
object will be filtered, containing only undamaged cells (See parameter:filter_output
)
- Alternatively, you can use a
Seurat
object as input
SRR1234567 <- limiric(
project_name = "SRR1234567",
seurat_input = seurat_object,
output_path = "/home/user/alignment/limiric/"
)
- If you have more than one sample, you may want to define a sample list
instead of running each individually.
You can do this through the
sample_list
parameter.
sample_list <- list(
list(project_name = "SRR1234567",
filtered_path = "/home/user/alignment/SRR1234567/filtered/",
output_path = "/home/user/alignment/limiric/"),
list(project_name = "SRR1234568",
filtered_path = "/home/user/alignment/SRR1234568/filtered/",
output_path = "/home/user/alignment/limiric/"),
list(project_name = "SRR1234569",
filtered_path = "/home/user/alignment/SRR1234569/filtered/",
output_path = "/home/user/alignment/limiric/")
)
GSE1234567 <- limiric(sample_list = sample_list)
NB Please note that you must define your sample list with each parameter specified, i.e.
limiric
won't know what to do if your list looks like this :sample_list <- list( list("SRR1234567", "/home/user/alignment/SRR1234567/filtered/", "/home/user/alignment/limiric/"), list("SRR1234568", "/home/user/alignment/SRR1234568/filtered/", "/home/user/alignment/limiric/"), list("SRR1234569", "/home/user/alignment/SRR1234569/filtered/", "/home/user/alignment/limiric/") )
To ensure the package is running smoothly on your device, access the available test data (test_data
) and
run the limiric
function with the following parameters (ensures test_run is as quick as possible).
# Load example Seurat object from the limiric package
data("test_data", package = "limiric")
test <- limiric(
project_name = "test_run",
filter_rbc = FALSE,
seurat_input = test_data,
output_path = tempdir()
)
If anything other than the output below is displayed, please check the installation of prerequisites or open an issue
on this GitHub
page.
# Beginning limiric analysis for test_run ...
# β Seurat object created
# β limiric damaged cell predictions
# β limiric analysis complete.
Note: This shouldn't take more than 10 seconds to run but it will vary depending on your machine.
Before interpretting limiric
's outputs, it may be helpful to understand a bit of what's going on in the background.
As you know, the magic of scRNA-seq revolves around the identification of distinct cell populations. In majority of scRNA-seq analysis workflows, distinct populations are identified according to the
clusters that form when dimensionality reduction is performed on the most variable genes in a sample. For instance, the typical Seurat
workflow uses the top two or three thousand variable genes.
In our case, instead of trying to isolate distinct cell-type associated populations, we are only interested in two broad populations: damaged and healthy cells. In general, most of what we know about how these two populations differ can be summarized by looking at the expression of mitochondrial and ribosomal genes. Thus, if we perform dimensionality reduction and clustering using only these genes, we expect to see a division of cells into two clusters associated with the cell's status as either damaged or healthy.
This is the basis of the low dimension mitochondrial & ribosomal clustering
, aka limiric
, algorithm.
The limiric
output will be created in the output_path
directory with the following structure
output_path/
|
βββ RBCQC
|
βββ CellQC
|
βββ Filtered
Before damaged cells can be identified, limiric
removes red blood cells, or cells that are highly contaminated with haemoglobin, from your data. This is done under the assumption that red blood cells will not be informative to your study. If this assumption should not be true, you can avoid this filtering using filter_rbc = FALSE
.
NB Given many scRNA-seq protocols, such as the
10X Genomics
protocol, advise for globin treatment, we hope for the contamination percentage to be as low as possible.
The output here comes in the form of a scatter plot where the red blood cells are coloured in blue. The percentage of the total cells that were removed is also given in the plot. |
This directory contains core diagnostic plots for the limiric
damaged cell detection algorithm. As shown below, you will see four tSNE plots. In this example dataset, you can see the cells divide into two distinct clusters; but which contain damaged cells and which contain healthy cells?
From literature we know that damaged cells have high mitochondrial expression and low ribosomal expression. This is largely because damaged cells, such as those undergoing apoptosis, are characterised by compromised cell membranes. In this case, free cytoplasmic RNA- like ribosomal RNA- is more likely to have escaped before being captured and sequenced, meaning its expression will be low. While the RNA enclosed within the mitochondria, which has its own membrane, is retained, meaning its expression will be high. But many other factors likely contribute to this phenomenon, including impaired ribosomal biogenesis in damaged cells.
Now answering the question of which cluster is which is easy with the limiric
tSNEs where the mitochondrial and ribosomal gene expression of each cell is made visible.
- A
Mitochondrial gene expression
tSNE shows that cells in the smaller cluster express mitochondrial genes at a very high level, suggesting they are likely damaged.
- The same conclusion can be reached looking at the
Ribosomal gene expression
tSNE where cells in the smaller cluster express ribosomal genes at a very low level.
- The
Complexity score
measures the total number of mitochondrial and ribosomal genes expressed in a cell. Cells that express a large number of these genes are more likely to be metabolically active, undamaged cells while those that only express a few are likely not. In theComplexity score
tSNE, the smaller cluster contains cells with very low complexities. This verifies by another metric that these cells are likely damaged.
Together, these metrics are used by the limiric
algorithm to annotate the damaged cells, as shown in the fourth and final tSNE. In summary, by looking at the limiric
tSNE plots, we can immediately tell that we have a small cluster of cells that are likley damaged and should be removed from the sample. This makes sense as, unless something went drastically wrong during sample preparations, you expect there to be far more healthy than damaged cells in your scRNA-seq data.
The main output of the limiric
function comes in the form of a barcodes.csv
containing annotations for each cell barcode of the input data. This can easily be incorporated into an existing scRNA-seq analysis workflow for filtering (see example).
Barcode | Limiric |
---|---|
AAACCTGAGATAGGAG | cell |
AAACCTGAGCTATGCT | cell |
AAACCTGAGCTGTTCA | damaged |
AAACCTGCACATTAGC | damaged |
... | ... |
Additionally, limiric
will output a Seurat
object with ready-filtered barcodes for seamless integration at the start of a Seurat
pre-processing workflow.
Standard good practice highly advises to correct for ambient RNA in your scRNAseq data. If you haven't already performed some kind of correction,
the SoupX
parameter allows you to do so. This will occur before anything else in the limiric
workflow.
SRR1234567 <- limiric(
project_name = "SRR1234567",
filtered_path = "/home/user/alignment/SRR1234567/filtered/",
soupx = TRUE,
raw_path = "/home/user/alignment/SRR1234567/raw/",
output_path = "/home/user/alignment/limiric/"
)
If you have a sample where only the immune cells are of interest, include the following isolate_cd45
parameter. This will isolate the immune cells present in the sample, then identify damaged cells.
SRR1234567 <- limiric(
project_name = "SRR1234567",
filtered_path = "/home/user/alignment/SRR1234567/filtered/",
isolate_cd45 = TRUE,
output_path = "/home/user/alignment/limiric/"
)
NB This will change your output directory structure by adding a new
IMCQC
layeroutput_path/ βββ CellQC | βββ IMCQC | βββ RBCQC | βββ Filtered
This output, like RBCQC, contains a scatter plot showing the removed cell in blue and the retained cells in grey. |
Detect damaged cells and compare results with those from DropletQC
.
SRR1234567 <- limiric(
project_name = "SRR1234567",
filtered_path = "/home/user/alignment/SRR1234567/filtered/",
droplet_qc = TRUE,
velocyto_path = "/home/user/alignment/velocyto/",
output_path = "/home/user/alignment/limiric/"
)
NB This will change your output directory structure by adding a new
DropletQC
layeroutput_path/ βββ CellQC | βββ DropletQC | βββ RBCQC | βββ Filtered
This will output a scatter plot and tSNE showing the cells annotated as damaged by both limiric and DropletQC .
|
Perform ambient RNA correction with SoupX
, filter red blood cells, isolate immune cells, detect damaged cells, and compare against DropletQC
.
SRR1234567 <- limiric(
project_name = "SRR1234567",
filtered_path = "/home/user/alignment/SRR1234567/filtered/",
soupx = TRUE,
raw_path = "/home/user/alignment/SRR1234567/raw/",
droplet_qc = TRUE,
isolate_cd45 = TRUE,
velocyto_path = "/home/user/alignment/velocyto/",
output_path = "/home/user/alignment/limiric/"
)
NB This will mean your output directory looks like this
output_path/ βββ CellQC | βββ DropletQC | βββ IMCQC | βββ RBCQC | βββ Filtered
sample_list <- list(
list(project_name = "SRR1234567",
filtered_path = "/home/user/alignment/SRR1234567/filtered/",
soupx = TRUE,
raw_path = "/home/user/alignment/SRR1234567/raw/",
droplet_qc = TRUE,
isolate_cd45 = TRUE,
velocyto_path = "/home/user/alignment/velocyto/",
output_path = "/home/user/alignment/limiric/"),
list(project_name = "SRR1234568",
filtered_path = "/home/user/alignment/SRR1234568/filtered/",
soupx = TRUE,
raw_path = "/home/user/alignment/SRR1234568/raw/",
droplet_qc = TRUE,
isolate_cd45 = TRUE,
velocyto_path = "/home/user/alignment/velocyto/",
output_path = "/home/user/alignment/limiric/"),
list(project_name = "SRR1234569",
filtered_path = "/home/user/alignment/SRR1234569/filtered/",
soupx = TRUE,
raw_path = "/home/user/alignment/SRR1234569/raw/",
droplet_qc = TRUE,
isolate_cd45 = TRUE,
velocyto_path = "/home/user/alignment/velocyto/",
output_path = "/home/user/alignment/limiric/")
)
GSE1234567 <- limiric(sample_list = sample_list)
If your alignment output files are not zipped (end with a .gz
extension), you will need to find a way to do this
before using limiric
. If you have a Linux
or mac
machine you can open the terminal in the directory where your files are stored and use gzip
cd path/to/directory
gzip *
This assumes that your files are the only items inside the directory. As with the standard output of many alignment algorithms such as
STARsolo
andCellRanger
, each sample should be stored in its own directory with the following file naming convention :path/ | βββ matrix.mtx | βββ barcodes.tsv | βββ features.tsv
If you have a Windows
machine, the simplest solution is to install Windows Subsystem for Linux
π§ which creates a new terminal environment for you with Linux
capabilites. From there, you can do the same as above. Note that WSL
uses a different path structure to access file systems where Windows
directories are mounted under /mnt/
cd /mnt/c/Users/path/to/directory
gzip *
# Add output to pre-existing Seurat object
limiric_annotations <- read.csv2("path/to/project_name_barcodes.csv",
sep = ",",
col.names = c("barcodes", "limiric"))
seurat@meta.data$limiric <- limiric_annotations$limiric[match(rownames(seurat@meta.data), limiric_annotations$barcodes)]
# Using pre-existing dimensionality reductions, visualise the limiric annotations
limiric_visual <- DimPlot(seurat, group.by = limiric)
# Filter cells marked as damaged by limiric
seurat_filtered <- subset(seurat, limiric == "cell")
seurat_filtered$limiric <- NULL # once used, remove the column