Using harmony in Seurat

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
library(harmony)
library(Seurat)
library(dplyr)
library(cowplot)

Introduction

This tutorial describes how to use harmony in Seurat v5 single-cell analysis workflows. RunHarmony() is a generic function is designed to interact with Seurat objects. This vignette will walkthrough basic workflow of Harmony with Seurat objects. Also, it will provide some basic downstream analyses demonstrating the properties of harmonized cell embeddings and a brief explanation of the exposed algorithm parameters.

## Install latest branch of harmony
## devtools::install_github('immunogenomics/harmony', force = TRUE)

Generating the dataset

For this demo, we will be aligning two groups of PBMCs Kang et al., 2017. In this experiment, PBMCs are in stimulated and control conditions. The stimulated PBMC group was treated with interferon beta.

Download Data

The full dataset used for this vignette are located under the following zenodo directory https://zenodo.org/record/8164711

Generate SeuratObject

## Source required data
data("pbmc_stim")
pbmc <- CreateSeuratObject(counts = cbind(pbmc.stim, pbmc.ctrl), project = "PBMC", min.cells = 5)

## Separate conditions

pbmc@meta.data$stim <- c(rep("STIM", ncol(pbmc.stim)), rep("CTRL", ncol(pbmc.ctrl)))

Running Harmony

Harmony works on an existing matrix with cell embeddings and outputs its transformed version with the datasets aligned according to some user-defined experimental conditions. By default, harmony will look up the pca cell embeddings and use these to run harmony. Therefore, it assumes that the Seurat object has these embeddings already precomputed.

Calculate PCA cell embeddings

Here, using Seurat::NormalizeData(), we will be generating a union of highly variable genes using each condition (the control and stimulated cells). These features are going to be subsequently used to generate the 20 PCs with Seurat::RunPCA().

pbmc <- pbmc %>%
    NormalizeData(verbose = FALSE)

VariableFeatures(pbmc) <- split(row.names(pbmc@meta.data), pbmc@meta.data$stim) %>% lapply(function(cells_use) {
    pbmc[,cells_use] %>%
        FindVariableFeatures(selection.method = "vst", nfeatures = 2000) %>% 
        VariableFeatures()
}) %>% unlist %>% unique

pbmc <- pbmc %>% 
    ScaleData(verbose = FALSE) %>% 
    RunPCA(features = VariableFeatures(pbmc), npcs = 20, verbose = FALSE)

Perform an integrated analysis

To run harmony on Seurat object after it has been normalized, only one argument needs to be specified which contains the batch covariate located in the metadata. For this vignette, further parameters are specified to align the dataset but the minimum parameters are shown in the snippet below:

## run harmony with default parameters
pbmc <- pbmc %>% RunHarmony("stim")
## is equivalent to:
pbmc <- RunHarmony(pbmc, "stim")

Here, we will be running harmony with some indicative parameters and plotting the convergence plot to illustrate some of the under the hood functionality.

pbmc <- pbmc %>% 
    RunHarmony("stim", plot_convergence = TRUE, nclust = 50, max_iter = 10, early_stop = T)

Harmony API parameters on Seurat objects

RunHarmony has several parameters accessible to users which are outlined below.

object (required)

The Seurat object. This vignette assumes Seurat objects are version 5.

group.by.vars (required)

A character vector that specifies all the experimental covariates to be corrected/harmonized by the algorithm.

When using RunHarmony() with Seurat, harmony will look up the group.by.vars metadata fields in the Seurat Object metadata.

For example, given the pbmc[["stim"]] exists as the stim condition, setting group.by.vars="stim" will perform integration of these samples accordingly. If you want to integrate on another variable, it needs to be present in Seurat object's meta.data.

To correct for several covariates, specify them in a vector: group.by.vars = c("stim", "new_covariate").

reduction.use

The cell embeddings to be used for the batch alignment. This parameter assumes that a reduced dimension already exists in the reduction slot of the Seurat object. By default, the pca reduction is used.

dims.use

Optional parameter which can use a name vector to select specific dimensions to be harmonized.

Algorithm parameters

Harmony Algorithm Overview{width=100%}

nclust

is a positive integer. Under the hood, harmony applies k-means soft-clustering. For this task, k needs to be determined. nclust corresponds to k. The harmonization results and performance are not particularly sensitive for a reasonable range of this parameter value. If this parameter is not set, harmony will autodetermine this based on the dataset size with a maximum cap of 200. For dataset with a vast amount of different cell types and batches this pamameter may need to be determined manually.

sigma

a positive scalar that controls the soft clustering probability assignment of single-cells to different clusters. Larger values will assign a larger probability to distant clusters of cells resulting in a different correction profile. Single-cells are assigned to clusters by their euclidean distance $d$ to some cluster center $Y$ after cosine normalization which is defined in the range [0,4]. The clustering probability of each cell is calculated as $e^{-\frac{d}{\sigma}}$ where $\sigma$ is controlled by the sigma parameter. Default value of sigma is 0.1 and it generally works well since it defines probability assignment of a cell in the range $[e^{-40}, e^0]$. Larger values of sigma restrict the dynamic range of probabilities that can be assigned to cells. For example, sigma=1 will yield a probabilities in the range of $[e^{-4}, e^0]$.

theta

theta is a positive scalar vector that determines the coefficient of harmony's diversity penalty for each corrected experimental covariate. In challenging experimental conditions, increasing theta may result in better integration results. Theta is an expontential parameter of the diversity penalty, thus setting theta=0 disables this penalty while increasing it to greater values than 1 will perform more aggressive corrections in an expontential manner. By default, it will set theta=2 for each experimental covariate.

max_iter

The number of correction steps harmony will perform before completing the data set integration. In general, more iterations than necessary increases computational runtime especially which becomes evident in bigger datasets. Setting early_stop=TRUE may reduce the actual number of correction steps which will be smaller than max_iter.

early_stop

Under the hood, harmony minimizes its objective function through a series of clustering and integration tests. By setting early_stop=TRUE, when the objective function is less than 1e-4 after a correction step harmony exits before reaching the max_iter correction steps. This parameter can drastically reduce run-time in bigger datasets.

.options

A set of internal algorithm parameters that can be overriden. For advanced users only.

Seurat specific parameters

These parameters are Seurat-specific and do not affect the flow of the algorithm.

project_dim

Toggle-like parameter, by default project_dim=TRUE. When enabled, RunHarmony() calculates genomic feature loadings using Seurat's ProjectDim() that correspond to the harmonized cell embeddings.

reduction.save

The new Reduced Dimension slot identifier. By default, reduction.save=TRUE. This option allows several independent runs of harmony to be retained in the appropriate slots in the SeuratObjects. It is useful if you want to try Harmony with multiple parameters and save them as e.g. 'harmony_theta0', 'harmony_theta1', 'harmony_theta2'.

Miscellaneous parameters

These parameters help users troubleshoot harmony.

plot_convergence

Option that plots the convergence plot after the execution of the algorithm. By default FALSE. Setting it to TRUE will collect harmony's objective value and plot it allowing the user to troubleshoot the flow of the algorithm and fine-tune the parameters of the dataset integration procedure.

Accessing the data

RunHarmony() returns the Seurat object which contains the harmonized cell embeddings in a slot named harmony. This entry can be accessed via pbmc@reductions$harmony. To access the values of the cell embeddings we can also use:

harmony.embeddings <- Embeddings(pbmc, reduction = "harmony")

Inspection of the modalities

After Harmony integration, we should inspect the quality of the harmonization and contrast it with the unharmonized algorithm input. Ideally, cells from different conditions will align along the Harmonized PCs. If they are not, you could increase the theta value above to force a more aggressive fit of the dataset and rerun the workflow.

p1 <- DimPlot(object = pbmc, reduction = "harmony", pt.size = .1, group.by = "stim")
p2 <- VlnPlot(object = pbmc, features = "harmony_1", group.by = "stim",  pt.size = .1)
plot_grid(p1,p2)

Plot Genes correlated with the Harmonized PCs

DimHeatmap(object = pbmc, reduction = "harmony", cells = 500, dims = 1:3)

Using harmony embeddings for dimensionality reduction in Seurat

The harmonized cell embeddings generated by harmony can be used for further integrated analyses. In this workflow, the Seurat object contains the harmony reduction modality name in the method that requires it.

Perform clustering using the harmonized vectors of cells

pbmc <- pbmc %>%
    FindNeighbors(reduction = "harmony") %>%
    FindClusters(resolution = 0.5) 

TSNE dimensionality reduction

pbmc <- pbmc %>%
    RunTSNE(reduction = "harmony")


p1 <- DimPlot(pbmc, reduction = "tsne", group.by = "stim", pt.size = .1)
p2 <- DimPlot(pbmc, reduction = "tsne", label = TRUE, pt.size = .1)
plot_grid(p1, p2)

One important observation is to assess that the harmonized data contain biological states of the cells. Therefore by checking the following genes we can see that biological cell states are preserved after harmonization.

FeaturePlot(object = pbmc, features= c("CD3D", "SELL", "CREM", "CD8A", "GNLY", "CD79A", "FCGR3A", "CCL2", "PPBP"), 
            min.cutoff = "q9", cols = c("lightgrey", "blue"), pt.size = 0.5)

UMAP

Very similarly with TSNE we can run UMAP by passing the harmony reduction in the function.

pbmc <- pbmc %>%
    RunUMAP(reduction = "harmony",  dims = 1:20)

p1 <- DimPlot(pbmc, reduction = "umap", group.by = "stim", pt.size = .1)
p2 <- DimPlot(pbmc, reduction = "umap", label = TRUE,  pt.size = .1)
plot_grid(p1, p2)
sessionInfo()


Try the harmony package in your browser

Any scripts or data that you put into this service are public.

harmony documentation built on Oct. 24, 2023, 1:06 a.m.