This vignette describes how to infer transcription factor activity from scRNA-seq data by running Viper on DoRothEA’s curated regulons.


DoRothEA is a comprehensive resource containing a curated collection of transcription factors (TFs) and its transcriptional targets. The set of genes regulated by a specific transcription factor is known as regulon. DoRothEA’s regulons were gathered from different types of evidence. Each TF-target interaction is defined by a confidence level based on the number of supporting evidence. The confidence levels ranges from A (highest confidence) to E (lowest confidence) (Garcia-Alonso et al. 2019).

DoRothEA regulons are usually coupled with the statistical method VIPER (Alvarez et al. 2016). In this context, TF activities are computed based on the mRNA expression levels of its targets. We therefore can consider TF activity as a proxy of a given transcriptional state (Dugourd and Saez-Rodriguez 2019).

Holland et al. (2020) evaluated the performance of DoRothEA in combination with VIPER when applied to scRNA-seq data. We showed that, in spite of the current limitations of scRNA-seq technologies, their approach can provide meaningful results in this context. Indeed, this vignette shows an example on how to apply DoRothEA regulons coupled with VIPER in a well known single-cell dataset.


First of all, you need a current version of R (http://www.r-project.org). DoRothEA is a freely available annotation package deposited on http://bioconductor.org/ and https://github.com/saezlab/dorothea.

You can install it by running the following commands on an R console:

if (!requireNamespace("BiocManager", quietly = TRUE))


We also load here the packages required to run this script.

## We load the required packages

Example of usage

In the following paragraphs, we provide examples describing how to run VIPER on DoRothEA regulons in a scRNA-seq dataset. In particular, we use the Seurat toolkit for single cell genomics (Stuart et al. 2019). For the sake of simplicity, we follow the example provided in the following Seurat vignette:


The dataset contains 2700 Peripheral Blood Mononuclear Cells (PBMC) that were sequenced on the Illumina NextSeq 500. This dataset is freely available in 10X Genomics:


## Load the PBMC dataset
pbmc.data <- Read10X(data.dir = "filtered_gene_bc_matrices/hg19/")

## Initialize the Seurat object with the raw (non-normalized data).
pbmc <- CreateSeuratObject(counts = pbmc.data, project = "pbmc3k",
                           min.cells = 3, min.features = 200)

Pre-processing, normalization and identification of highly variable features

We follow the standard pre-processing steps as described in the aforementioned Seurat vignette before going deeper into the data analysis. These steps carry out the selection and filtration of cells based on quality control metrics, the data normalization and scaling, and the detection of highly variable features (see https://satijalab.org/seurat/v3.1/pbmc3k_tutorial.html).

## Identification of mithocondrial genes
pbmc[["percent.mt"]] <- PercentageFeatureSet(pbmc, pattern = "^MT-")

## Filtering cells following standard QC criteria.
pbmc <- subset(pbmc, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 &
    percent.mt < 5)

## Normalizing the data
pbmc <- NormalizeData(pbmc, normalization.method = "LogNormalize",
    scale.factor = 10000)

pbmc <- NormalizeData(pbmc)

## Identify the 2000 most highly variable genes
pbmc <- FindVariableFeatures(pbmc, selection.method = "vst", nfeatures = 2000)

## In addition we scale the data
all.genes <- rownames(pbmc)
pbmc <- ScaleData(pbmc, features = all.genes)

Clustering cells

One of the most relevant steps in scRNA-seq data analysis is clustering. Cells are grouped based on the similarity of their transcriptomic profiles. We first apply the Seurat v3 classical approach as described in their aforementioned vignette. We visualize the cell clusters using UMAP:

pbmc <- RunPCA(pbmc, features = VariableFeatures(object = pbmc),
               verbose = FALSE)
pbmc <- FindNeighbors(pbmc, dims = 1:10, verbose = FALSE)
pbmc <- FindClusters(pbmc, resolution = 0.5, verbose = FALSE)
pbmc <- RunUMAP(pbmc, dims = 1:10, umap.method = "uwot", metric = "cosine")

pbmc.markers <- FindAllMarkers(pbmc, only.pos = TRUE, min.pct = 0.25,
                               logfc.threshold = 0.25, verbose = FALSE)

## Assigning cell type identity to clusters
new.cluster.ids <- c("Naive CD4 T", "Memory CD4 T", "CD14+ Mono", "B", "CD8 T",
                     "FCGR3A+ Mono", "NK", "DC", "Platelet")
names(new.cluster.ids) <- levels(pbmc)
pbmc <- RenameIdents(pbmc, new.cluster.ids)
DimPlot(pbmc, reduction = "umap", label = TRUE, pt.size = 0.5) + NoLegend()

Clustering cells with TF activity

Holland et al. (2020) showed that clustering the cells based on their TF activity profiles can also be very interesting. Indeed, clustering cells using TF activity computed with VIPER and DoRothEA performs better than using the expression level of the same TFs. In addition, it brings complementary information to the clusters based on transcriptomics profiles.

Here, we first run VIPER on DoRothEA’s regulons to obtain TFs activity, by using the wrapper function run_viper(). This function can deal with different input types such as matrix, dataframe, ExpressionSet or even Seurat objects. In case of a seurat object the function returns the same seurat object with an additonal assay called dorothea containing the TF activities in the slot data.

## We read Dorothea Regulons for Human:
dorothea_regulon_human <- get(data("dorothea_hs", package = "dorothea"))

## We obtain the regulons based on interactions with confidence level A, B and C
regulon <- dorothea_regulon_human %>%
    dplyr::filter(confidence %in% c("A","B","C"))

## We compute Viper Scores 
pbmc <- run_viper(pbmc, regulon,
                  options = list(method = "scale", minsize = 4,
                                 eset.filter = FALSE, cores = 1,
                                 verbose = FALSE))

We then apply Seurat to cluster the cells following the same protocol than above but using TF activity scores.

## We compute the Nearest Neighbours to perform cluster
DefaultAssay(object = pbmc) <- "dorothea"
pbmc <- ScaleData(pbmc)
pbmc <- RunPCA(pbmc, features = rownames(pbmc), verbose = FALSE)
pbmc <- FindNeighbors(pbmc, dims = 1:10, verbose = FALSE)
pbmc <- FindClusters(pbmc, resolution = 0.5, verbose = FALSE)

pbmc <- RunUMAP(pbmc, dims = 1:10, umap.method = "uwot", metric = "cosine")

pbmc.markers <- FindAllMarkers(pbmc, only.pos = TRUE, min.pct = 0.25,
                               logfc.threshold = 0.25, verbose = FALSE)

## Assigning cell type identity to clusters
new.cluster.ids <- c("Naive CD4 T", "Memory CD4 T", "CD14+ Mono", "B", "CD8 T",
                     "FCGR3A+ Mono", "NK", "DC", "Platelet")
names(new.cluster.ids) <- levels(pbmc)
pbmc <- RenameIdents(pbmc, new.cluster.ids)
## Warning: Cannot find identity NA

## Warning: Cannot find identity NA
DimPlot(pbmc, reduction = "umap", label = TRUE, pt.size = 0.5) + NoLegend()

TF activity per cell population

Finally, we characterise the different cell populations based on their TF activities thanks to the previously computed VIPER scores on DoRothEA’s regulons.

## We transform Viper scores, scaled by seurat, into a data frame to better 
## handling the results
viper_scores_df <- GetAssayData(pbmc, slot = "scale.data",
                                    assay = "dorothea") %>%
  data.frame() %>%

## We create a data frame containing the cells and their clusters
CellsClusters <- data.frame(cell = names(Idents(pbmc)),
                            cell_type = as.character(Idents(pbmc)),
                            stringsAsFactors = FALSE)

## We create a data frame with the Viper score per cell and its clusters
viper_scores_clusters <- viper_scores_df  %>%
  data.frame() %>%
  rownames_to_column("cell") %>%
  gather(tf, activity, -cell) %>%

## We summarize the Viper scores by cellpopulation
summarized_viper_scores <- viper_scores_clusters %>%
  group_by(tf, cell_type) %>%
  summarise(avg = mean(activity),
            std = sd(activity))

For visualization purposes, we select the 20 most variable TFs across clusters according to our scores.

## We select the 20 most variable TFs. (20*9 populations = 180)
highly_variable_tfs <- summarized_viper_scores %>%
  group_by(tf) %>%
  mutate(var = var(avg))  %>%
  ungroup() %>%
  top_n(180, var) %>%

## We prepare the data for the plot
summarized_viper_scores_df <- summarized_viper_scores %>%
  semi_join(highly_variable_tfs, by = "tf") %>%
  dplyr::select(-std) %>%
  spread(tf, avg) %>%
  data.frame(row.names = 1, check.names = FALSE, stringsAsFactors = FALSE)
palette_length = 100
my_color = colorRampPalette(c("Darkblue", "white","red"))(palette_length)

my_breaks <- c(seq(min(summarized_viper_scores_df), 0,
                   length.out=ceiling(palette_length/2) + 1),

viper_hmap <- pheatmap(t(summarized_viper_scores_df),fontsize=14,
                       fontsize_row = 10,
                       color=my_color, breaks = my_breaks,
                       main = "DoRothEA (ABC)", angle_col = 45,
                       treeheight_col = 0,  border_color = NA)

Session info

## R version 4.0.1 (2020-06-06)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS High Sierra 10.13.6
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## attached base packages:
## [1] parallel  stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## other attached packages:
##  [1] viper_1.22.0        Biobase_2.48.0      BiocGenerics_0.34.0
##  [4] tidyr_1.1.0         pheatmap_1.0.12     tibble_3.0.1       
##  [7] Seurat_3.1.5        dplyr_1.0.0         dorothea_1.0.1     
## [10] BiocStyle_2.16.0   
## loaded via a namespace (and not attached):
##  [1] segmented_1.1-0     nlme_3.1-148        tsne_0.1-3         
##  [4] fs_1.4.1            RcppAnnoy_0.0.16    RColorBrewer_1.1-2 
##  [7] httr_1.4.1          rprojroot_1.3-2     sctransform_0.2.1  
## [10] tools_4.0.1         backports_1.1.7     R6_2.4.1           
## [13] irlba_2.3.3         KernSmooth_2.23-17  uwot_0.1.8         
## [16] lazyeval_0.2.2      colorspace_1.4-1    tidyselect_1.1.0   
## [19] gridExtra_2.3       compiler_4.0.1      desc_1.2.0         
## [22] plotly_4.9.2.1      labeling_0.3        bookdown_0.19      
## [25] scales_1.1.1        lmtest_0.9-37       ggridges_0.5.2     
## [28] pbapply_1.4-2       pkgdown_1.5.1       stringr_1.4.0      
## [31] digest_0.6.25       mixtools_1.2.0      rmarkdown_2.2      
## [34] pkgconfig_2.0.3     htmltools_0.4.0     bcellViper_1.24.0  
## [37] htmlwidgets_1.5.1   rlang_0.4.6         farver_2.0.3       
## [40] generics_0.0.2      zoo_1.8-8           jsonlite_1.6.1     
## [43] ica_1.0-2           magrittr_1.5        patchwork_1.0.0    
## [46] Matrix_1.2-18       Rcpp_1.0.4.6        munsell_0.5.0      
## [49] ape_5.4             reticulate_1.16     lifecycle_0.2.0    
## [52] stringi_1.4.6       yaml_2.2.1          MASS_7.3-51.6      
## [55] Rtsne_0.15          plyr_1.8.6          grid_4.0.1         
## [58] listenv_0.8.0       ggrepel_0.8.2       crayon_1.3.4       
## [61] lattice_0.20-41     cowplot_1.0.0       splines_4.0.1      
## [64] knitr_1.28          pillar_1.4.4        igraph_1.2.5       
## [67] future.apply_1.5.0  reshape2_1.4.4      codetools_0.2-16   
## [70] leiden_0.3.3        glue_1.4.1          evaluate_0.14      
## [73] data.table_1.12.8   BiocManager_1.30.10 vctrs_0.3.1        
## [76] png_0.1-7           gtable_0.3.0        RANN_2.6.1         
## [79] purrr_0.3.4         kernlab_0.9-29      future_1.17.0      
## [82] assertthat_0.2.1    ggplot2_3.3.1       xfun_0.14          
## [85] rsvd_1.0.3          RSpectra_0.16-0     e1071_1.7-3        
## [88] class_7.3-17        survival_3.1-12     viridisLite_0.3.0  
## [91] memoise_1.1.0       cluster_2.1.0       globals_0.12.5     
## [94] fitdistrplus_1.1-1  ellipsis_0.3.1      ROCR_1.0-11


Alvarez, Mariano J, Yao Shen, Federico M Giorgi, Alexander Lachmann, B Belinda Ding, B Hilda Ye, and Andrea Califano. 2016. “Functional Characterization of Somatic Mutations in Cancer Using Network-Based Inference of Protein Activity.” Nature Genetics 48 (8). Springer Science; Business Media LLC: 838–47. https://doi.org/10.1038/ng.3593.

Dugourd, Aurelien, and Julio Saez-Rodriguez. 2019. “Footprint-Based Functional Analysis of Multiomic Data.” Current Opinion in Systems Biology 15 (June). Elsevier BV: 82–90. https://doi.org/10.1016/j.coisb.2019.04.002.

Garcia-Alonso, Luz, Christian H. Holland, Mahmoud M. Ibrahim, Denes Turei, and Julio Saez-Rodriguez. 2019. “Benchmark and Integration of Resources for the Estimation of Human Transcription Factor Activities.” Genome Research 29 (8). Cold Spring Harbor Laboratory: 1363–75. https://doi.org/10.1101/gr.240663.118.

Holland, Christian H., Jovan Tanevski, Javier Perales-Patón, Jan Gleixner, Manu P. Kumar, Elisabetta Mereu, Brian A. Joughin, et al. 2020. “Robustness and Applicability of Transcription Factor and Pathway Analysis Tools on Single-Cell RNA-Seq Data.” Genome Biology 21 (1). Springer Science; Business Media LLC. https://doi.org/10.1186/s13059-020-1949-z.

Stuart, Tim, Andrew Butler, Paul Hoffman, Christoph Hafemeister, Efthymia Papalexi, William M. Mauck, Yuhan Hao, Marlon Stoeckius, Peter Smibert, and Rahul Satija. 2019. “Comprehensive Integration of Single-Cell Data.” Cell 177 (7). Elsevier BV: 1888–1902.e21. https://doi.org/10.1016/j.cell.2019.05.031.