10 Single cell visualization

The following section describes typical approaches for visualizing single-cell data.

This chapter is divided into three parts. Section 10.2 will highlight visualization approaches downstream of cell type classification from Section 9.3. We will then focus on visualization methods that relate single-cell data to the sample level in Section 10.3. Lastly, Section 10.4 will provide a more customized example on how to integrate various single-cell and sample metadata into one heatmap using the ComplexHeatmap package (Gu, Eils, and Schlesner 2016).

Visualization functions from popular R packages in single-cell research such as scater, DittoSeq and CATALYST will be utilized. We will recycle methods and functions that we have used in previous sections, while also introducing new ones.

Please note that this chapter aims to provide an overview on common visualization options and should be seen as a stepping-stone. However, many more options exist and the user should customize the visualization according to the biological question at hand.

10.1 Load data

First, we will read in the previously generated SpatialExperiment object.

spe <- readRDS("data/spe.rds")

For visualization purposes, we will define markers that were used for cell type classification and markers that can indicate a specific cell state (e.g., Ki67 for proliferating cells).

# Define cell phenotype markers 
type_markers <- c("Ecad", "CD45RO", "CD20", "CD3", "FOXP3", "CD206", "MPO", 
                  "SMA", "CD8a", "CD4", "HLADR", "CD15", "CD38", "PDGFRb")

# Define cell state markers 
state_markers <- c("CarbonicAnhydrase", "Ki67", "PD1", "GrzB", "PDL1", 
                   "ICOS", "TCF7", "VISTA")

# Add to spe
rowData(spe)$marker_class <- ifelse(rownames(spe) %in% type_markers, "type",
                                    ifelse(rownames(spe) %in% state_markers, "state", 
                                    "other"))

10.2 Cell-type level

In the first section of this chapter, the grouping-level for the visualization approaches will be the cell type classification from Section 9.3. Other grouping levels (e.g., cluster assignments from Section 9.2) are possible and the user should adjust depending on the chosen analysis workflow.

10.2.1 Dimensionality reduction visualization

As seen before, we can visualize single-cells in low-dimensional space. Often, non-linear methods for dimensionality reduction such as tSNE and UMAP are used. They aim to preserve the distances between each cell and its neighbors in the high-dimensional space.

Interpreting these plots is not trivial, but local neighborhoods in the plot can suggest similarity in expression for given cells. See Orchestrating Single-Cell Analysis with Bioconductor for more details.

Here, we will use dittoDimPlot from the DittoSeq package and plotReducedDim from the scater package to visualize the fastMNN-corrected UMAP colored by cell type and expression (using the asinh-transformed intensities), respectively.

Both functions are highly flexible and return ggplot objects which can be further modified.

library(dittoSeq)
library(scater)
library(patchwork)
library(cowplot)
library(viridis)

## UMAP colored by cell type and expression - dittoDimPlot
p1 <- dittoDimPlot(spe, 
                   var = "celltype", 
                   reduction.use = "UMAP_mnnCorrected", 
                   size = 0.2,
                   do.label = TRUE) +
  scale_color_manual(values = metadata(spe)$color_vectors$celltype) +
  theme(legend.title = element_blank()) +
  ggtitle("Cell types on UMAP, integrated cells")

p2 <- dittoDimPlot(spe, 
                   var = "Ecad", 
                   assay = "exprs",
                   reduction.use = "UMAP_mnnCorrected", 
                   size = 0.2, 
                   colors = viridis(100), 
                   do.label = TRUE) +
    scale_color_viridis()
  
p1 + p2

The plotReducedDim function of the scater package provides an alternative way for visualizing cells in low dimensions. Here, we loop over all type markers, generate one plot per marker and plot the indivudual plots side-by-side.

# UMAP colored by expression for all markers - plotReducedDim
plot_list  <- lapply(rownames(spe)[rowData(spe)$marker_class == "type"], function(x){
                      p <- plotReducedDim(spe, 
                                          dimred = "UMAP_mnnCorrected",
                                          colour_by = x,
                                          by_exprs_values = "exprs",
                                          point_size = 0.2)
                      return(p)
                    })

plot_grid(plotlist = plot_list)

10.2.2 Heatmap visualization

Next, it is often useful to visualize single-cell expression per cell type in form of a heatmap. For this, we will use the dittoHeatmap function from the DittoSeq package.

We sub-sample the dataset to 4000 cells for ease of visualization and overlay the cancer type and patient ID from which the cells were extracted.

set.seed(220818)
cur_cells <- sample(seq_len(ncol(spe)), 4000)

# Heatmap visualization - DittoHeatmap
dittoHeatmap(spe[,cur_cells], 
             genes = rownames(spe)[rowData(spe)$marker_class == "type"],
             assay = "exprs", 
             cluster_cols = FALSE, 
             scale = "none",
             heatmap.colors = viridis(100), 
             annot.by = c("celltype", "indication", "patient_id"),
             annotation_colors = list(indication = metadata(spe)$color_vectors$indication,
                                      patient_id = metadata(spe)$color_vectors$patient_id,
                                      celltype = metadata(spe)$color_vectors$celltype))

Similarly, we can visualize the mean marker expression per cell type for all cells bu first calculating the mean marker expression per cell type using the aggregateAcrossCells function from the scuttle package and then use dittoHeatmap. We will annotate the heatmap with the number of cells per cell type and we will used different ways for feature scaling.

library(scuttle)

## aggregate by cell type
celltype_mean <- aggregateAcrossCells(as(spe, "SingleCellExperiment"),  
                     ids = spe$celltype, 
                     statistics = "mean",
                     use.assay.type = "exprs", 
                     subset.row = rownames(spe)[rowData(spe)$marker_class == "type"])

# No scaling
dittoHeatmap(celltype_mean,
             assay = "exprs", 
             cluster_cols = TRUE, 
             scale = "none",
             heatmap.colors = viridis(100),
             annot.by = c("celltype", "ncells"),
             annotation_colors = list(celltype = metadata(spe)$color_vectors$celltype,
                                      ncells = plasma(100)))

# Scaled to max
dittoHeatmap(celltype_mean,
             assay = "exprs", 
             cluster_cols = TRUE, 
             scaled.to.max = TRUE,
             heatmap.colors.max.scaled = inferno(100),
             annot.by = c("celltype", "ncells"),
             annotation_colors = list(celltype = metadata(spe)$color_vectors$celltype,
                                      ncells = plasma(100)))

# Z score scaled
dittoHeatmap(celltype_mean,
             assay = "exprs", 
             cluster_cols = TRUE, 
             annot.by = c("celltype", "ncells"),
             annotation_colors = list(celltype = metadata(spe)$color_vectors$celltype,
                                      ncells = plasma(100)))

As illustrated above for not-, max-, and Z score-scaled expression values, different ways of scaling can have strong effects on visualization output and we encourage the user to test multiple options.

Overall, we can observe cell-type specific marker expression (e.g., Tumor = Ecad high and B cells = CD20 high) in agreement with the gating scheme of Section 9.3.

10.2.3 Violin plot visualization

The plotExpression function from the scater package allows to plot the distribution of expression values across cell types for a chosen set of proteins. The output is a ggplot object which can be modified further.

# Violin Plot - plotExpression
plotExpression(spe[,cur_cells], 
               features = rownames(spe)[rowData(spe)$marker_class == "type"],
               x = "celltype", 
               exprs_values = "exprs", 
               colour_by = "celltype") +
    theme(axis.text.x =  element_text(angle = 90))+
    scale_color_manual(values = metadata(spe)$color_vectors$celltype)

10.2.4 Scatter plot visualization

Moreover, a protein expression based scatter plot can be generated with dittoScatterPlot (returns a ggplot object). We overlay the plot with the cell type information.

# Scatter plot
dittoScatterPlot(spe, 
                 x.var = "CD3", 
                 y.var="CD20", 
                 assay.x = "exprs", 
                 assay.y = "exprs", 
                 color.var = "celltype") +
    scale_color_manual(values = metadata(spe)$color_vectors$celltype) +
    ggtitle("Scatterplot for CD3/CD20 labelled by celltype")

We can nicely observe how the “B next to T cell” phenotype (BnTcell) has high expression values for both CD20 and CD3.

Of note, in a setting where the user aims to assign labels to clusters based on marker genes/proteins, all of the above plots can be particularly helpful.

10.2.5 Barplot visualization

In order to display frequencies of cell types per sample/patient, the dittoBarPlot function will be used. Data can be represented as percentages or counts and again ggplot objects are outputted.

# by sample_id - percentage
dittoBarPlot(spe, 
             var = "celltype", 
             group.by = "sample_id") +
    scale_fill_manual(values = metadata(spe)$color_vectors$celltype)

# by patient_id - percentage
dittoBarPlot(spe, 
             var = "celltype", 
             group.by = "patient_id") +
    scale_fill_manual(values = metadata(spe)$color_vectors$celltype)

# by patient_id - count
dittoBarPlot(spe, 
             scale = "count",
             var = "celltype", 
             group.by = "patient_id") +
    scale_fill_manual(values = metadata(spe)$color_vectors$celltype)

We can see that cell type frequencies change between samples/patients and that the highest proportion/counts of plasma cells and stromal cells can be observed for Patient 2 and Patient 4, respectively.

10.2.6 CATALYST-based visualization

In the following, we highlight some useful visualization functions from the CATALYST package.

To this end, we will first convert the SpatialExperiment object into a CATALYST-compatible format.

library(CATALYST)

# Save SPE in CATALYST-compatible object with renamed colData entries and 
# new metadata information
spe_cat <- spe 

spe_cat$sample_id <- factor(spe$sample_id)
spe_cat$condition <- factor(spe$indication)
spe_cat$cluster_id <- factor(spe$celltype)

# Add celltype information to metadata
metadata(spe_cat)$cluster_codes <- data.frame(celltype = factor(spe_cat$celltype))

All of the CATALYST functions presented below return ggplot objects, which allow flexible downstream adjustment.

10.2.6.1 Pseudobulk-level MDS plot

Pseudobulk-level multi-dimensional scaling (MDS) plots can be rendered with the exported pbMDS function.

Here, we will use pbMDS to highlight expression similarities between cell types and subsequently for each celltype-sample-combination.

# MDS pseudobulk by cell type
pbMDS(spe_cat, 
      by = "cluster_id", 
      features = rownames(spe_cat)[rowData(spe_cat)$marker_class == "type"], 
      label_by = "cluster_id", 
      k = "celltype") +
  scale_color_manual(values = metadata(spe_cat)$color_vectors$celltype)

# MDS pseudobulk by cell type and sample_id
pbMDS(spe_cat, 
      by = "both", 
      features = rownames(spe_cat)[rowData(spe_cat)$marker_class == "type"], 
      k = "celltype", 
      shape_by = "condition", 
      size_by = TRUE) +
  scale_color_manual(values = metadata(spe_cat)$color_vectors$celltype)

We can see that the pseudobulk-expression profile of neutrophils seems markedly distinct from the other cell types, while comparable cell types such as the T cell subtypes group together. Furthermore, pseudobulk cell-type profiles from SCCHN appear different from the other indications.

10.2.6.2 Reduced dimension plot on CLR of proportions

The clrDR function produces dimensionality reduction plots on centered log-ratios (CLR) of sample/cell type proportions across cell type/samples.

As with pbMDS, the output plots aim to illustrate the degree of similarity between cell types based on sample proportions.

# CLR on cluster proportions across samples
clrDR(spe_cat, 
      dr = "PCA", 
      by = "cluster_id", 
      k = "celltype", 
      label_by = "cluster_id", 
      arrow_col = "sample_id", 
      point_pal = metadata(spe_cat)$color_vectors$celltype) 

We can again observe that neutrophils have a divergent profile also in terms of their sample proportions.

10.2.6.3 Pseudobulk expression boxplot

The plotPbExprs generates combined box- and jitter-plots of aggregated marker expression per cell type and sample (image). Here, we further split the data by cancer type.

plotPbExprs(spe_cat, 
            k = "celltype", 
            facet_by = "cluster_id", 
            ncol = 2, 
            features = rownames(spe_cat)[rowData(spe_cat)$marker_class == "type"]) +
    scale_color_manual(values = metadata(spe_cat)$color_vectors$indication)

Notably, CD15 levels are elevated in SCCHN in comparison to all other indications for most cell types.

10.3 Sample-level

In the next section, we will shift the grouping-level focus from the cell type to the sample-level. Sample-levels will be further divided into the sample-(image) and patient-level.

Although we will mostly repeat the functions from the previous section 10.2, sample- and patient-level centered visualization can provide additional quality control and biological interpretation.

10.3.1 Dimensionality reduction visualization

Visualization of low-dimensional embeddings, here comparing non-corrected and fastMNN-corrected UMAPs, and coloring it by sample-levels is often used for “batch effect” assessment as mentioned in Section 7.4.

We will again use dittoDimPlot.

## UMAP colored by cell type and expression - dittoDimPlot
p1 <- dittoDimPlot(spe, 
                   var = "sample_id",
                   reduction.use = "UMAP", 
                   size = 0.2, 
                   colors = viridis(100), 
                   do.label = FALSE) +
    scale_color_manual(values = metadata(spe)$color_vectors$sample_id) +
  theme(legend.title = element_blank()) +
  ggtitle("Sample ID")

p2 <- dittoDimPlot(spe, 
                   var = "sample_id",
                   reduction.use = "UMAP_mnnCorrected", 
                   size = 0.2, 
                   colors = viridis(100), 
                   do.label = FALSE) +
    scale_color_manual(values = metadata(spe)$color_vectors$sample_id) +
  theme(legend.title = element_blank()) +
  ggtitle("Sample ID")

p3 <- dittoDimPlot(spe, 
                   var = "patient_id",
                   reduction.use = "UMAP", 
                   size = 0.2,
                   do.label = FALSE) +
  scale_color_manual(values = metadata(spe)$color_vectors$patient_id) +
  theme(legend.title = element_blank()) +
  ggtitle("Patient ID")

p4 <- dittoDimPlot(spe, 
                   var = "patient_id", 
                   reduction.use = "UMAP_mnnCorrected", 
                   size = 0.2,
                   do.label = FALSE) +
  scale_color_manual(values = metadata(spe)$color_vectors$patient_id) +
  theme(legend.title = element_blank()) +
  ggtitle("Patient ID")

(p1 + p2) / (p3 + p4)

As illustrated in Section 8, we see that the fastMNN approach (right side of the plot) leads to mixing of cells across samples/patients and thus batch effect correction.

10.3.2 Heatmap visualization

It can be beneficial to use a heatmap to visualize single-cell expression per sample and patient. Such a plot, which we will create using dittoHeatmap, can highlight biological differences across samples/patients.

# Heatmap visualization - DittoHeatmap
dittoHeatmap(spe[,cur_cells], 
             genes = rownames(spe)[rowData(spe)$marker_class == "type"],
             assay = "exprs", 
             order.by = c("patient_id","sample_id"),
             cluster_cols = FALSE, 
             scale = "none",
             heatmap.colors = viridis(100), 
             annot.by = c("celltype", "indication", "patient_id", "sample_id"),
             annotation_colors = list(celltype = metadata(spe)$color_vectors$celltype,
                                      indication = metadata(spe)$color_vectors$indication,
                                      patient_id = metadata(spe)$color_vectors$patient_id,
                                      sample_id = metadata(spe)$color_vectors$sample_id))

As in Section 7.3, aggregated mean marker expression per sample/patient allow identification of samples/patients with outlying expression patterns.

Here, we will focus on the patient level and use aggregateAcrossCells and dittoHeatmap. The heatmap will be annotated with the number of cells per patient and cancer type and displayed using two scaling options.

# mean expression by patient_id
patient_mean <- aggregateAcrossCells(as(spe, "SingleCellExperiment"),  
                     ids = spe$patient_id, 
                     statistics = "mean",
                     use.assay.type = "exprs", 
                     subset.row = rownames(spe)[rowData(spe)$marker_class == "type"])

# No scaling
dittoHeatmap(patient_mean,
             assay = "exprs", 
             cluster_cols = TRUE, 
             scale = "none",
             heatmap.colors = viridis(100),
             annot.by = c("patient_id","indication","ncells"),
             annotation_colors = list(patient_id = metadata(spe)$color_vectors$patient_id,
                                      indication = metadata(spe)$color_vectors$indication,
                                      ncells = plasma(100)))

# Max expression scaling
dittoHeatmap(patient_mean,
             assay = "exprs", 
             cluster_cols = TRUE, 
             scaled.to.max =  TRUE,
             heatmap.colors.max.scaled = inferno(100),
             annot.by = c("patient_id","indication","ncells"),
             annotation_colors = list(patient_id = metadata(spe)$color_vectors$patient_id,
                                      indication = metadata(spe)$color_vectors$indication,
                                      ncells = plasma(100)))

As seen before, CD15 levels are elevated in Patient 4 (SCCHN), while SMA levels are highest for Patient 4 (CRC).

10.3.3 Barplot visualization

Complementary to displaying cell type frequencies per sample/patient, we can use dittoBarPlot to display sample/patient frequencies per cell type.

dittoBarPlot(spe, 
             var = "patient_id", 
             group.by = "celltype") +
    scale_fill_manual(values = metadata(spe)$color_vectors$patient_id)

dittoBarPlot(spe, 
             var = "sample_id", 
             group.by = "celltype") +
    scale_fill_manual(values = metadata(spe)$color_vectors$sample_id)

Patient2 has the highest and lowest proportion of plasma cells and neutrophils, respectively.

10.3.4 CATALYST-based visualization

10.3.4.1 Pseudobulk-level MDS plot

Expression-based pseudobulks for each sample can be compared with the pbMDS function.

# MDS pseudobulk by sample_id 
pbMDS(spe_cat, 
      by = "sample_id", 
      color_by = "sample_id", 
      features = rownames(spe_cat)[rowData(spe_cat)$marker_class == "type"]) +
  scale_color_manual(values = metadata(spe_cat)$color_vectors$sample_id)

There are marked differences in pseudobulk-expression patterns between samples and across patients, which can be driven by biological differences and also technical aspects such as divergent region selection.

10.3.4.2 Reduced dimension plot on CLR of proportions

The clrDR function can also be used to analyze similarity of samples based on cell type proportions.

# CLR on sample proportions across clusters
clrDR(spe_cat, 
      dr = "PCA", 
      by = "sample_id", 
      point_col = "sample_id",
      k = "celltype", 
      point_pal = metadata(spe_cat)$color_vectors$sample_id) +
  scale_color_manual(values = metadata(spe_cat)$color_vectors$celltype)
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.

There are notable differences between samples based on their cell type proportions.

Interestingly, Patient3_001, Patient1_003, Patient4_007 and Patient4_006 group together and the PC loadings indicate a strong contribution of BnT and B cells, which could propose formation of tertiary lymphoid structures (TLS). In section 12.2, we will be able to confirm this hypothesis visually on the images.

10.4 Further examples

In the last section of this chapter, we will use the popular ComplexHeatmap package to create a visualization example that combines various cell-type- and sample-level information.

ComplexHeatmap is highly versatile and is originally inspired from the pheatmap package. Therefore, many arguments have the same/similar names.

For more details, we would recommend to read the reference book.

10.4.1 Publication-ready ComplexHeatmap

For this example, we will concatenate heatmaps and annotations horizontally into one rich heatmap list. The grouping-level for the visualization will again be the cell type information from Section 9.3

Initially, we will create two separate Heatmap objects for cell type and state markers.

Then, metadata information, including the cancer type proportion and number of cells/patients per cell type, will be extracted into HeatmapAnnotation objects.

Notably, we will add spatial features per cell type, here the number of neighbors extracted from colPair(spe) and cell area, in another HeatmapAnnotation object.

Ultimately, all objects are combined in a HeatmapList and visualized.

library(ComplexHeatmap)
library(circlize)
library(tidyverse)
set.seed(22)

### 1. Heatmap bodies ###

# Heatmap body color 
col_exprs <- colorRamp2(c(0,1,2,3,4), 
                        c("#440154FF","#3B518BFF","#20938CFF",
                          "#6ACD5AFF","#FDE725FF"))

# Create Heatmap objects
# By cell type markers
celltype_mean <- aggregateAcrossCells(as(spe, "SingleCellExperiment"),  
                     ids = spe$celltype, 
                     statistics = "mean",
                     use.assay.type = "exprs", 
                     subset.row = rownames(spe)[rowData(spe)$marker_class == "type"])

h_type <- Heatmap(t(assay(celltype_mean, "exprs")),
        column_title = "type_markers",
        col = col_exprs,
        name= "mean exprs",
        show_row_names = TRUE, 
        show_column_names = TRUE)
    
# By cell state markers
cellstate_mean <- aggregateAcrossCells(as(spe, "SingleCellExperiment"),  
                     ids = spe$celltype, 
                     statistics = "mean",
                     use.assay.type = "exprs", 
                     subset.row = rownames(spe)[rowData(spe)$marker_class == "state"])

h_state <- Heatmap(t(assay(cellstate_mean, "exprs")),
        column_title = "state_markers",
        col = col_exprs,
        name= "mean exprs",
        show_row_names = TRUE,
        show_column_names = TRUE)


### 2. Heatmap annotation ###

### 2.1  Metadata features

anno <- colData(celltype_mean) %>% as.data.frame %>% select(celltype, ncells)

# Proportion of indication per celltype
indication <- unclass(prop.table(table(spe$celltype, spe$indication), margin = 1))

# Number of contributing patients per celltype
cluster_PID <- colData(spe) %>% 
    as.data.frame() %>% 
    select(celltype, patient_id) %>% 
    group_by(celltype) %>% table() %>% 
    as.data.frame()

n_PID <- cluster_PID %>% 
    filter(Freq>0) %>% 
    group_by(celltype) %>% 
    count(name = "n_PID") %>% 
    column_to_rownames("celltype")

# Create HeatmapAnnotation objects
ha_anno <- HeatmapAnnotation(celltype = anno$celltype,
                            border = TRUE, 
                            gap = unit(1,"mm"),
                            col = list(celltype = metadata(spe)$color_vectors$celltype),
                            which = "row")
    
ha_meta <- HeatmapAnnotation(n_cells = anno_barplot(anno$ncells, width = unit(10, "mm")),
                            n_PID = anno_barplot(n_PID, width = unit(10, "mm")),
                            indication = anno_barplot(indication,width = unit(10, "mm"),
                                                      gp = gpar(fill = metadata(spe)$color_vectors$indication)),
                            border = TRUE, 
                            annotation_name_rot = 90,
                            gap = unit(1,"mm"),
                            which = "row")

### 2.2 Spatial features

# Add number of neighbors to spe object (saved in colPair)
spe$n_neighbors <- countLnodeHits(colPair(spe, "neighborhood"))

# Select spatial features and average over celltypes
spatial <- colData(spe) %>% 
    as.data.frame() %>% 
    select(area, celltype, n_neighbors)

spatial <- spatial %>% 
    select(-celltype) %>% 
    aggregate(by = list(celltype = spatial$celltype), FUN = mean) %>% 
    column_to_rownames("celltype")

# Create HeatmapAnnotation object
ha_spatial <- HeatmapAnnotation(
    area = spatial$area,
    n_neighbors = spatial$n_neighbors,
    border = TRUE,
    gap = unit(1,"mm"),
    which = "row")

### 3. Plot rich heatmap ###

# Create HeatmapList object
h_list <- h_type +
    h_state +
    ha_anno +
    ha_spatial +
    ha_meta

# Add customized legend for anno_barplot()
lgd <- Legend(title = "indication", 
              at = colnames(indication), 
              legend_gp = gpar(fill = metadata(spe)$color_vectors$indication))
             
# Plot
draw(h_list,annotation_legend_list = list(lgd))

This plot summarizes most of the information we have seen in this chapter previously. In addition, we can observe that tumor cells have the largest mean cell area, high number of neighbors and elevated Ki67 expression. BnT cells have the highest number of neighbors on average, which is biological sound given their predominant location in highly immune infiltrated regions (such as TLS).

10.4.2 Interactive visualization

For interactive visualization of the single-cell data the iSEE shiny application can be used. For a comprehensive tutorial, please refer to the iSEE vignette.

if (interactive()) {
    library(iSEE)

    iSEE(spe)   
}

10.5 Session Info

SessionInfo
## R version 4.3.2 (2023-10-31)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 22.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## time zone: Etc/UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] grid      stats4    stats     graphics  grDevices utils     datasets 
## [8] methods   base     
## 
## other attached packages:
##  [1] lubridate_1.9.3             forcats_1.0.0              
##  [3] stringr_1.5.1               dplyr_1.1.4                
##  [5] purrr_1.0.2                 readr_2.1.4                
##  [7] tidyr_1.3.0                 tibble_3.2.1               
##  [9] tidyverse_2.0.0             circlize_0.4.15            
## [11] ComplexHeatmap_2.18.0       CATALYST_1.26.0            
## [13] viridis_0.6.4               viridisLite_0.4.2          
## [15] cowplot_1.1.2               patchwork_1.1.3            
## [17] scater_1.30.1               scuttle_1.12.0             
## [19] dittoSeq_1.14.0             ggplot2_3.4.4              
## [21] SpatialExperiment_1.12.0    SingleCellExperiment_1.24.0
## [23] SummarizedExperiment_1.32.0 Biobase_2.62.0             
## [25] GenomicRanges_1.54.1        GenomeInfoDb_1.38.5        
## [27] IRanges_2.36.0              S4Vectors_0.40.2           
## [29] BiocGenerics_0.48.1         MatrixGenerics_1.14.0      
## [31] matrixStats_1.2.0          
## 
## loaded via a namespace (and not attached):
##   [1] RColorBrewer_1.1-3          jsonlite_1.8.8             
##   [3] shape_1.4.6                 magrittr_2.0.3             
##   [5] magick_2.8.2                ggbeeswarm_0.7.2           
##   [7] TH.data_1.1-2               farver_2.1.1               
##   [9] rmarkdown_2.25              GlobalOptions_0.1.2        
##  [11] zlibbioc_1.48.0             vctrs_0.6.5                
##  [13] Cairo_1.6-2                 DelayedMatrixStats_1.24.0  
##  [15] RCurl_1.98-1.13             rstatix_0.7.2              
##  [17] htmltools_0.5.7             S4Arrays_1.2.0             
##  [19] plotrix_3.8-4               BiocNeighbors_1.20.1       
##  [21] broom_1.0.5                 SparseArray_1.2.3          
##  [23] sass_0.4.8                  bslib_0.6.1                
##  [25] plyr_1.8.9                  sandwich_3.1-0             
##  [27] zoo_1.8-12                  cachem_1.0.8               
##  [29] igraph_1.6.0                lifecycle_1.0.4            
##  [31] iterators_1.0.14            pkgconfig_2.0.3            
##  [33] rsvd_1.0.5                  Matrix_1.6-4               
##  [35] R6_2.5.1                    fastmap_1.1.1              
##  [37] GenomeInfoDbData_1.2.11     clue_0.3-65                
##  [39] digest_0.6.33               colorspace_2.1-0           
##  [41] ggnewscale_0.4.9            irlba_2.3.5.1              
##  [43] ggpubr_0.6.0                beachmat_2.18.0            
##  [45] labeling_0.4.3              cytolib_2.14.0             
##  [47] timechange_0.2.0            fansi_1.0.6                
##  [49] colorRamps_2.3.1            nnls_1.5                   
##  [51] polyclip_1.10-6             abind_1.4-5                
##  [53] compiler_4.3.2              withr_2.5.2                
##  [55] doParallel_1.0.17           ConsensusClusterPlus_1.66.0
##  [57] backports_1.4.1             BiocParallel_1.36.0        
##  [59] carData_3.0-5               highr_0.10                 
##  [61] ggforce_0.4.1               ggsignif_0.6.4             
##  [63] MASS_7.3-60                 drc_3.0-1                  
##  [65] DelayedArray_0.28.0         rjson_0.2.21               
##  [67] FlowSOM_2.10.0              gtools_3.9.5               
##  [69] tools_4.3.2                 vipor_0.4.7                
##  [71] beeswarm_0.4.0              glue_1.6.2                 
##  [73] Rtsne_0.17                  cluster_2.1.4              
##  [75] reshape2_1.4.4              generics_0.1.3             
##  [77] gtable_0.3.4                tzdb_0.4.0                 
##  [79] hms_1.1.3                   data.table_1.14.10         
##  [81] BiocSingular_1.18.0         ScaledMatrix_1.10.0        
##  [83] car_3.1-2                   utf8_1.2.4                 
##  [85] XVector_0.42.0              ggrepel_0.9.4              
##  [87] foreach_1.5.2               pillar_1.9.0               
##  [89] splines_4.3.2               flowCore_2.14.0            
##  [91] tweenr_2.0.2                lattice_0.21-9             
##  [93] survival_3.5-7              RProtoBufLib_2.14.0        
##  [95] tidyselect_1.2.0            knitr_1.45                 
##  [97] gridExtra_2.3               bookdown_0.37              
##  [99] xfun_0.41                   pheatmap_1.0.12            
## [101] stringi_1.8.3               yaml_2.3.8                 
## [103] evaluate_0.23               codetools_0.2-19           
## [105] cli_3.6.2                   munsell_0.5.0              
## [107] jquerylib_0.1.4             Rcpp_1.0.11                
## [109] png_0.1-8                   XML_3.99-0.16              
## [111] parallel_4.3.2              sparseMatrixStats_1.14.0   
## [113] bitops_1.0-7                mvtnorm_1.2-4              
## [115] scales_1.3.0                ggridges_0.5.5             
## [117] crayon_1.5.2                GetoptLong_1.0.5           
## [119] rlang_1.1.2                 multcomp_1.4-25

References

Gu, Zuguang, Roland Eils, and Matthias Schlesner. 2016. “Complex Heatmaps Reveal Patterns and Correlations in Multidimensional Genomic Data.” Bioinformatics 32: 2847–49.