In this tutorial, we demonstrate how to use Monocle 3 (alpha release) to resolve multiple disjoint trajectories.
Intructions to use Monocle 3:
source("https://bioconductor.org/biocLite.R")
biocLite("monocle")
devtools::install_github("cole-trapnell-lab/DDRTree", ref="simple-ppt-like")
devtools::install_github("cole-trapnell-lab/L1-graph")
install.packages("reticulate")
library(reticulate)
py_install("umap-learn")
py_install("louvain")
devtools::install_github("cole-trapnell-lab/monocle-release", ref="monocle3_beta_release")
You may find that some depdencies are not automatically installed. If so, try to source them from bioconductor using biocLite().
Monocle 3 takes as input an expression matrix and, depdending on how you run it, can produce several outputs:
To analyze a single-cell dataset, Monocle first normalizes expression values to account for technical variation in RNA recovery and sequencing depth. Next, to eliminate noise and make downstream computations more tractable, it projects each cell onto the top 50 (by default) principal components. Then, you as the user choose whether to reduce the dimensionality further using one of two non-linear methods for dimensionality reduction: t-SNE
or UMAP
. The former is an extremely popular and widely accepted technique for visualizing single-cell RNA-seq data. The latter is faster, and often better preserves the global structure of the data but is also newer and therefore less well tested by the single-cell community. Then, Monocle 3 will cluster your cells, organize them into trajectories, or both.
Monocle 3 can learn multiple disconnected or "disjoint" trajectories. This is important because many experiments will capture a community of cells that are responding to a stimulus or undergoing differentiation, with each type of cell responding differently. Where Monocle 2 would incorrectly force them all into a single trajectory, Monocle 3 can detect that some cells are part of a different process than others in the dataset. Monocle 3 achieves this by "partitioning" the cells into "supergroups" using a method derived from "approximate graph abstraction" (AGA) (Wolf et al, 2017). Cells from different supergroups cannot be part of the same trajectory.
Monocle 3 provides three different ways to organize cells into trajectories, all of which are based on the concept of "principal graph embedding". DDRTree
is the method used in Monocle 2 to learn tree-like trajectories, and has received some important updates in Monocle 3. SimplePPT
works similarly to DDRTree
in that it learns a tree-like trajectory, but it does not attempt to further reduce the dimensionality of the data. L1Graph
is an advanced optimization method that can learn trajectories that have loops in them (that is, trajectories that aren't trees).
Once this is complete, you can run tests for genes that are specific to each cluster, find genes that vary over the course of a trajectory, and plot your data in many different ways. Monocle 3 provides a suite of regression tests to find genes that differ between clusters and over trajectories. Monocle 3 also introduces a new test that uses the principal graph directly and can help find genes that vary in complex ways over a trajectory with loops and more intricate structures.
rm(list = ls()) # clear the environment
#load all the necessary libraries
options(warn=-1) # turn off warning message globally
library(monocle)
First let's load up the data from Paul et. al, which explored hematopoeisis at single-cell resolution. To save time, we'll just load a CellDataSet object. Monocle 3 makes some changes to the CellDataSet class, so we'll call a function to "upgrade" the old object to the new format. Finally, we'll adjust one of the factor levels to make the cell types easier to read.
#cds <- readRDS('data/valid_subset_GSE72857_cds2.RDS')
cds <- readRDS(gzcon(url("http://trapnell-lab.gs.washington.edu/public_share/valid_subset_GSE72857_cds2.RDS")))
pData(cds)$cell_type2 <- plyr::revalue(as.character(pData(cds)$cluster),
c("1" = 'Erythrocyte',
"2" = 'Erythrocyte',
"3" = 'Erythrocyte',
"4" = 'Erythrocyte',
"5" = 'Erythrocyte',
"6" = 'Erythrocyte',
"7" = 'Multipotent progenitors',
"8" = 'Megakaryocytes',
"9" = 'GMP',
"10" = 'GMP',
"11" = 'Dendritic cells',
"12" = 'Basophils',
"13" = 'Basophils',
"14" = 'Monocytes',
"15" = 'Monocytes',
"16" = 'Neutrophils',
"17" = 'Neutrophils',
"18" = 'Eosinophls',
"19" = 'lymphoid'))
detailed_cell_type_color <- c("Basophils" = "#E088B8", "Dendritic cells" = "#46C7EF", "Eosinophls" = "#EFAD1E", "Erythrocyte" = "#8CB3DF", "Monocytes" = "#53C0AD", "Multipotent progenitors" = "#4EB859", "GMP" = "#D097C4", "Megakaryocytes" = "#ACC436", "Neutrophils" = "#F5918A", 'NA' = '#000080')
# the definitiion of CellDataset in Monocle 3 has been updated, update old CDS with the updateCDS function
cds <- updateCDS(cds)
# Pass TRUE if you want to see progress output on some of Monocle 3's operations
DelayedArray:::set_verbose_block_processing(TRUE)
# Passing a higher value will make some computations faster but use more memory. Adjust with caution!
options(DelayedArray.block.size=1000e6)
We then estimate size factors for each cell and dispersion function for the genes in the cds as usual. Monocle 3 now performs these operations using the DelayedArray packages so they work on datasets with millions of cells.
cds <- estimateSizeFactors(cds)
cds <- estimateDispersions(cds)
Next, run the preprocessCDS()
function to project the data into lower PCA space:
cds <- preprocessCDS(cds, num_dim = 20)
Then, apply a further round of (nonlinear) dimensionality reduction using UMAP:
cds <- reduceDimension(cds, reduction_method = 'UMAP', python_home = "/Users/xqiu/.virtualenvs/r-reticulate/bin/python")
Now we're ready to start learning trajectories!
Rather than forcing all cells into a single developmental trajectory, Monocle 3 enables you to learn a set of trajectories that describe the biological process you're studying. For example, if you're looking at a community of immune cells responding to infection, each cell type will respond to antigen (and each other) in a different way, so they should be organized into distinct trajectories.
In Monocle 3, we recognize "disjoint" trajectories by drawing on ideas from Alex Wolf and colleagues, who recently introduced the concept of abstract graph participation. Monocle 3 implements the test for cell community connectedness from Wolf et al via the partitionCells()
function, which divides the cells into "supergroups".
cds <- partitionCells(cds)
Now that the cells are paritioned, we can organize each supergroup into a separate trajectory. The default method for doing this in Monocle 3 is SimplePPT
, which assumes that each trajectory is a tree (albeit one that may have multiple roots). Learn these trees with the learnGraph
function:
cds <- learnGraph(cds, RGE_method = 'SimplePPT')
plot_cell_trajectory(cds, color_by = "cell_type2") + scale_color_manual(values = detailed_cell_type_color)
You can see that Monocle has learned two trajectories, one of which is tiny and essentially trivial. The other has one major branch. SimplePPT essentially is finding a principal graph that fits nicely within the cells as projected in the UMAP coordinate space.
In the trajectory above, you can see that the "Multipotent progenitor" cells are located roughly in the middle of the longer trajectory segment. We know from extensive past study of hematopoeisis that these are the "root" cell state that generates all the others in the primary trajectory. We need to tell Monocle that these cells are the "beginning" of the trajectory. In Monocle 2, this wouldn't be possible, because the software required that the root be one of the leaves of the tree. Monocle 3 allows you to specify an internal part of the tree as the root. There are two ways to do this:
orderCells()
.orderCells()
with no root specified and it will open a window for you to select the root(s) with your mouse cursor. This latter option is only available in interactive sessions, and doesn't work in Jupyter notebooks.Once you select one or more roots, orderCells()
computes the shortest path from each cell's location on the princiapl graph to the nearest root node. That is, a cell's pseudotime value is the geodesic distance from it to the nearest root, traveling over the graph. Any cell that is not reachable from some root will be assigned a pseudotime value of infinity.
You may find it helpful to automatically pick the root according to any number of biologically-driven criteria. For example, you could find the nodes at which cells expressing a certain marker gene are concentrated. Or could select the node where cells from an early experimental timepoint land. Here, we provide one such helper function to show you how to do this kind of thing:
# a helper function to identify the root principal points:
get_correct_root_state <- function(cds, cell_phenotype, root_type){
cell_ids <- which(pData(cds)[, cell_phenotype] == root_type)
closest_vertex <- cds@auxOrderingData[[cds@dim_reduce_type]]$pr_graph_cell_proj_closest_vertex
closest_vertex <- as.matrix(closest_vertex[colnames(cds), ])
root_pr_nodes <- V(cds@minSpanningTree)$name[as.numeric(names(which.max(table(closest_vertex[cell_ids,]))))]
root_pr_nodes
}
In the above function, we are accessing pr_graph_cell_proj_closest_vertex
which is just a list that stores for each cell, the ID of the principal graph node it's closest to. This is handle for computing statistics (e.g. with dplyr
) about the principal graph nodes and which cells of what type map to them.
Now we can call this function to automatically find the node in the principal graph where our multipotent progenitors reside:
MPP_node_ids = get_correct_root_state(cds, cell_phenotype = 'cell_type2', "Multipotent progenitors")
cds <- orderCells(cds, root_pr_nodes = MPP_node_ids)
plot_cell_trajectory(cds)
Sometimes, projecting cells into three dimensions instead of two can make the biological process you're studying easier to interpret. In Monocle 3, we provide the plot_3d_cell_trajectory
function to plot a dataset in 3 dimensions. The block of code below shows you how to learn a trajectory in 3D:
cds <- reduceDimension(cds, max_components = 3,
python_home = "/Users/xqiu/.virtualenvs/r-reticulate/bin/python",
reduction_method = 'UMAP',
metric="cosine",
verbose = F)
cds <- partitionCells(cds)
cds <- learnGraph(cds, max_components = 3, RGE_method = 'SimplePPT', partition_component = T,
verbose = F)
cds <- orderCells(cds,
root_pr_nodes = get_correct_root_state(cds,
cell_phenotype = 'cell_type2',
"Multipotent progenitors"))
plot_3d_cell_trajectory(cds,
color_by="cell_type2",
webGL_filename=paste(getwd(), "/trajectory_3D.html", sep=""),
palette=detailed_cell_type_color,
show_backbone=TRUE,
useNULL_GLdev=TRUE)
We are often interested in finding genes that are differentially expressed across a single-cell trajectory. Monocle 3 introduces a new approach for finding such genes that draws on a powerful technique in spatial correlation analysis, the Moran’s I test. Moran’s I is a measure of multi-directional and multi-dimensional spatial autocorrelation. The statistic tells you whether cells at nearby positions on a trajectory will have similar (or dissimilar) expression levels for the gene being tested. Although both Pearson correlation and Moran’s I ranges from -1 to 1, the interpretation of Moran’s I is slightly different: +1 means that nearby cells will have perfectly similar expression (as in the right panel below); 0 represents no correlation (center), and -1 means that neighboring cells will be anti-correlated (left).
In Monocle 3, we implement Moran's I through the principalGraphTest
function:
pr_graph_test <- principalGraphTest(cds, k=3, cores=1)
We can easily view the top differentially expressed genes as follows:
dplyr::add_rownames(pr_graph_test) %>%
dplyr::arrange(plyr::desc(morans_test_statistic), plyr::desc(-qval)) %>% head(3)
You can check out the test results for one or more genes like this:
pr_graph_test[fData(cds)$gene_short_name %in% c("Hbb-b1"),]
We can see that overall, there are more than 2,300 DE genes over the whole trajectory:
nrow(subset(pr_graph_test, qval < 0.01))
Once you've identified differentially expressed genes, you'll often want to visualize their expression levels on the trajectory. The plot bleow shows each cell with detectable levels of Hbb-b1 superimposed on the trajectory. Values are log-transformed and scaled into Z scores. Cells with no expression are not shown to avoid overplotting.
plot_3d_cell_trajectory(cds, markers = c('Hbb-b1'),
webGL_filename=paste(getwd(), "beta_globin.html", sep=""),
show_backbone=TRUE,
useNULL_GLdev=TRUE)
sessionInfo()