10  Skin: Deep phenotyping, cluster annotation, and exploration of Post vs Pre 3rd vaccination differences in cell proportions and expression

10.1 Set up Seurat workspace

# Load libraries
library(presto)
library(ggplot2)
library(Seurat)
library(tidyverse)
library(patchwork)
library(paletteer)
library(msigdbr)
library(ComplexHeatmap)
library(circlize)
library(SingleCellExperiment)
library(muscat)
library(limma)
library(scuttle)
library(lemon)
library(ggforce)
library(cowplot)
library(speckle)
library(knitr)

10.2 Load previous saved object

merged.18279.skin.singlets <- readRDS("Skin_scRNA_Part8.rds")

10.3 Set up cluster groupings by cell class

cluster_annot <- c(
        "0" = "MonoMac_1",
        "1_0" = "CD4_T_Naive/CM",
        "1_1" = "CD4_T_CTL",
        "2_0" = "CD8_T_EM_1",
        "2_1" = "CD8_T_Naive/CM_1",
        "2_2" = "CD8_T_EM_2",
        "3" = "MonoMac_2",
        "4_0" = "CD8_T_Naive/CM_2",
        "4_1" = "CD8_T_Naive/CM_3",
        "5_0" = "NK/T_1",
        "5_1" = "NK/T_2",
        "5_2" = "NK_cells",
        "6_0" = "T_unknown_1",
        "6_1" = "CD8_T_EM_3",
        "7" = "cDC2",
        "8" = "Keratinocytes",
        "9_0" = "Treg_1",
        "9_1" = "Treg_2",
        "10" = "Fibroblasts",
        "11_0" = "T_unknown_2",
        "11_1" = "T_unknown_3",
        "12" = "MonoMac_3",
        "13_0" = "Proliferating_T_cells_1",
        "13_1" = "Proliferating_T_cells_2",
        "14" = "pDC",
        "15" = "DC_LAMP3",
        "16" = "Mast",
        "17" = "Endothelial",
        "18" = "cDC1",
        "19_0" = "NK/T_3",
        "20" = "Pericytes/smooth_muscle",
        "21" = "Basal_1",
        "22" = "MonoMac_4",
        "23" = "B_cells",
        "24" = "Basal_2",
        "25" = "Macrophages",
        "26" = "Proliferating_cDC2"
)
clustLabels <- as.data.frame(cluster_annot)[merged.18279.skin.singlets$sub.cluster,]
merged.18279.skin.singlets <- AddMetaData(merged.18279.skin.singlets, list(clustLabels), col.name = "CellAnnotation")

10.4 Set up broader cell classes

cluster_classes <- enframe(cluster_annot,name = "sub.cluster",value = "CellAnnotation") %>%
  as_tibble() %>%
  mutate(CellClass = case_when(
    sub.cluster %in% c("0","3","12","22","25") ~ "Monocyte/\nMacrophage",
    sub.cluster %in% c("14","18","7","26","15") ~ "DC",
    sub.cluster %in% c("23") ~ "B",
    sub.cluster %in% c("5_2","5_0","5_1","19_0","2_1","4_0","4_1","2_0","2_2","6_1","1_0","1_1","9_0","9_1","13_0","13_1","6_0","11_0","11_1") ~ "NK/T",
    sub.cluster %in% c("16") ~ "Mast",
    sub.cluster %in% c("8","10","17","20","21","24") ~ "Non-immune"
    )
  ) %>%
  dplyr::select(-CellAnnotation) %>%
  column_to_rownames(var = "sub.cluster") %>%
  as.data.frame()

clustClasses <- cluster_classes[merged.18279.skin.singlets$sub.cluster,]
merged.18279.skin.singlets <- AddMetaData(merged.18279.skin.singlets, list(clustClasses), col.name = "CellClass")

10.5 Plot original UMAP labeled by sub-cluster

merged.18279.skin.singlets$CellAnnotation <- factor(x = merged.18279.skin.singlets$CellAnnotation, 
                                                    levels = as.character(cluster_annot))
DimPlot(merged.18279.skin.singlets, 
        label = FALSE, 
        reduction = "umap.harmony",
        group.by = "CellAnnotation")

10.5.1 Plot again split by Timepoint

merged.18279.skin.singlets$CellAnnotation <- factor(x = merged.18279.skin.singlets$CellAnnotation, 
                                                    levels = as.character(cluster_annot))
merged.18279.skin.singlets$Timepoint <- factor(x = merged.18279.skin.singlets$Timepoint, levels = c("Pre3rd", "Post3rd"))

DimPlot(merged.18279.skin.singlets, 
        label = FALSE, 
        reduction = "umap.harmony",
        group.by = "CellAnnotation",
        split.by = "Timepoint")

10.6 Plot original UMAP labeled by broader cell class

DimPlot(merged.18279.skin.singlets, 
        label = FALSE, 
        reduction = "umap.harmony",
        group.by = "CellClass")

10.6.1 Plot again except split by Timepoint

DimPlot(merged.18279.skin.singlets, 
        label = FALSE, 
        reduction = "umap.harmony",
        split.by = "Timepoint",
        group.by = "CellClass") +
  theme_classic() +
  theme(panel.border = element_rect(colour = "black",fill = NA)
        )

10.7 Now plot all cluster annotations using only ggplot functions, useful later

umap <- rownames_to_column(as.data.frame(Embeddings(merged.18279.skin.singlets,reduction = "umap.harmony")),var="Barcode") %>%
  as_tibble() %>%
  left_join(enframe(merged.18279.skin.singlets$sub.cluster,name = "Barcode",value="sub.cluster"),by="Barcode") %>%
  left_join(enframe(merged.18279.skin.singlets$CellAnnotation,name = "Barcode",value="CellAnnotation"),by="Barcode")

umap %>%
  ggplot(aes(x = umapharmony_1, y = umapharmony_2, color = CellAnnotation)) +
  geom_point(size=0.25) +
  theme_bw()

10.8 Plot UMAP split by Ipilimumab concentration cohort and Timepoint

rownames_to_column(as.data.frame(Embeddings(merged.18279.skin.singlets,reduction = "umap.harmony")),var="Barcode") %>%
    as_tibble() %>%
    left_join(enframe(merged.18279.skin.singlets$sub.cluster,name = "Barcode",value="sub.cluster"),by="Barcode") %>%
    left_join(enframe(merged.18279.skin.singlets$CellClass,name = "Barcode",value="CellClass"),by="Barcode") %>%
    mutate(Timepoint = str_split_i(Barcode,pattern = "_",i = 3)) %>%
    mutate(IpiCohort = str_split_i(Barcode,pattern = "_", i = 4)) %>%
    ggplot(aes(x = umapharmony_1, y = umapharmony_2, color = CellClass)) +
    geom_point(size = 0.25) +
    facet_grid(IpiCohort ~ fct_relevel(Timepoint,c("Pre3rd","Post3rd"))) +
    theme_bw() +
    geom_text(aes(x, y, label = lab),
              data = data.frame(x = 13, 
                                y = 14, 
                                lab = c(paste0("n = ", table(merged.18279.skin.singlets$IpiCohort,merged.18279.skin.singlets$Timepoint)[1,1]),
                                        paste0("n = ", table(merged.18279.skin.singlets$IpiCohort,merged.18279.skin.singlets$Timepoint)[1,2]), 
                                        paste0("n = ", table(merged.18279.skin.singlets$IpiCohort,merged.18279.skin.singlets$Timepoint)[2,1]),
                                        paste0("n = ", table(merged.18279.skin.singlets$IpiCohort,merged.18279.skin.singlets$Timepoint)[2,2])
                                        ),
                                Timepoint = c("Pre3rd","Post3rd","Pre3rd","Post3rd"),
                                IpiCohort = c("2.5mgIpi","2.5mgIpi","5mgIpi","5mgIpi")
                                ), 
                inherit.aes = FALSE
            )

10.9 Plot large heatmap demonstrating marker gene selectivity and cluster identity

merged.18279.skin.singlets[['RNA']] <- JoinLayers(merged.18279.skin.singlets[['RNA']])
merged.sce <- as.SingleCellExperiment(merged.18279.skin.singlets, assay="RNA")

(mergedCondition.sce <- prepSCE(merged.sce,
        kid = "sub.cluster",
        gid = "Timepoint",
        sid = "Sample",
        drop = TRUE))
class: SingleCellExperiment 
dim: 61217 33397 
metadata(1): experiment_info
assays(2): counts logcounts
rownames(61217): 5-8S-rRNA 5S-rRNA ... ZZEF1 ZZZ3
rowData names(0):
colnames(33397): P101_Skin_Pre3rd_2.5mgIpi_RNA_GCACATAAGTGCCATT
  P101_Skin_Pre3rd_2.5mgIpi_RNA_GGAACTTGTCGCGGTT ...
  P111_Skin_Post3rd_5mgIpi_RNA_ACGATGTTCCTAGGGC
  P111_Skin_Post3rd_5mgIpi_RNA_GAACATCAGCCACGTC
colData names(3): cluster_id sample_id group_id
reducedDimNames(4): PCA UMAP.UNINTEGRATED INTEGRATED.HARMONY
  UMAP.HARMONY
mainExpName: RNA
altExpNames(0):
all_genes_to_plot <- c("PTPRC","TCF4","TNFSF10","S100A8","S100A9","RNASE1","MARCO","SIGLEC1","CD14","ITGAM","ITGAX","FCN1","APOBEC3A","SERPINA1","FOLR2","LILRB5","CD163","APOE","FABP5","LIPA","APOC1","TREM2","CD68","CSF1R","FCGR3A","C1QA","C1QB","C1QC","ICAM1","LGALS9","CD40","CD80","CD86","HLA-DRA","HLA-DRB1","HLA-DQA1","HLA-DQA2","HLA-DQB1","HLA-DQB2","IL3RA","IRF4","IRF8","CLEC4C","CLEC9A","CLEC10A","CD1C","CD1E","FCER1A","CD207","MARCKSL1","FSCN1","LAMP3","LY75","FLT3","CD274","BANK1","CD79A","MS4A1","CD19","NCR1","NCAM1","CD3E","CD3G", "CD4","CD8A","TRAC","TRBC1","TRBC2","TRDC","IL7R","CCR7","SELL","CD27","TCF7","CXCR6","CCR6","NKG7","GNLY","GZMA","GZMB","PRF1","PDCD1","LAG3","TIGIT","CTLA4","TOX","BATF","TNFRSF4","ICOS","IL2RA","FOXP3","TNFRSF18","KIT","MKI67","TOP2A","HPGD","CPA3","SFN","CXCL14","CAV1","DCN","LUM","PECAM1","VWF","TAGLN","CALD1","KRT5","DMKN","TACSTD2")

cluster_order <- c("0","3","12","22","25","14","18","7","26","15","23","5_2","5_0","5_1","19_0","2_1","4_0","4_1","2_0","2_2","6_1","1_0","1_1","9_0","9_1","13_0","13_1","6_0","11_0","11_1","16","8","10","17","20","21","24")
slice_order <- factor(cluster_classes[cluster_order,], levels = unique(cluster_classes[cluster_order,]))

gene_categories <- enframe(all_genes_to_plot,name=NULL,value="Gene") %>%
  as_tibble() %>%
  mutate(GeneClass = case_when(
    Gene %in% c("PTPRC") ~ "Immune",
    Gene %in% c("TCF4","TNFSF10","S100A8","S100A9","RNASE1","MARCO","SIGLEC1","CD14","ITGAM","ITGAX","FCN1","APOBEC3A","SERPINA1","FOLR2","LILRB5","CD163","APOE","FABP5","LIPA","APOC1","TREM2","CD68","CSF1R","FCGR3A","C1QA","C1QB","C1QC","ICAM1","LGALS9") ~ "Monocyte/\nMacrophage",
    Gene %in% c("CD40","CD80","CD86","HLA-DRA","HLA-DRB1","HLA-DQA1","HLA-DQA2","HLA-DQB1","HLA-DQB2","IL3RA","IRF4","IRF8","CLEC4C","CLEC9A","CLEC10A","CD1C","CD1E","FCER1A","CD207","MARCKSL1","FSCN1","LAMP3","LY75","FLT3","CD274") ~ "Dendritic\ncell",
    Gene %in% c("BANK1","CD79A","MS4A1","CD19") ~ "B cell",
    Gene %in% c("NCR1","NCAM1") ~ "NK cell",
    Gene %in% c("CD3E","CD3G","CD4","CD8A","TRAC","TRBC1","TRBC2","TRDC","IL7R","CCR7","SELL","CD27","TCF7") ~ "T cell",
    Gene %in% c("CXCR6","CCR6","NKG7","GNLY","GZMA","GZMB","PRF1","PDCD1","LAG3","TIGIT","CTLA4","TOX","BATF","TNFRSF4","ICOS","IL2RA","FOXP3","TNFRSF18","KIT") ~ "T cell\nsubsets/\nNK cells",
    Gene %in% c("MKI67","TOP2A") ~ "Proliferation",
    Gene %in% c("SFN","CXCL14","CAV1") ~ "Keratinocyte",
    Gene %in% c("DCN","LUM") ~ "Fibroblast",
    Gene %in% c("HPGD","CPA3") ~ "Mast cell",
    Gene %in% c("PECAM1","VWF") ~ "Endothelial",
    Gene %in% c("TAGLN","CALD1") ~ "Pericyte/\nsmooth muscle",
    Gene %in% c("KRT5","DMKN","TACSTD2") ~ "Basal"
    )
  )
  
gene_slice_order <- factor(gene_categories$GeneClass, levels = unique(gene_categories$GeneClass))
  
pb_all <- aggregateData(mergedCondition.sce,
    assay = "counts",
    fun = "mean",
    by = "cluster_id")

all_genes_to_plot <- all_genes_to_plot[all_genes_to_plot %in% rownames(assay(pb_all))]
mat <- assay(pb_all)[all_genes_to_plot,cluster_order]
colnames(mat) <- cluster_annot[cluster_order]
ComplexHeatmap::Heatmap(mat,
                        col = circlize::colorRamp2(c(0,3),hcl_palette = "viridis"),
                        cluster_rows = FALSE,
                        cluster_columns = FALSE,
                        column_split = slice_order,
                        row_split = gene_slice_order,
                        row_title_rot = 0,
                        cluster_column_slices = FALSE,
                        border = TRUE,
                        row_names_gp = gpar(fontsize=6,fontface="italic"),
                        column_names_gp = gpar(fontsize=8),
                        column_title_gp = gpar(fontsize=8,fontface="bold"),
                        row_title_gp = gpar(fontsize=8,fontface="bold"),
                        name = "Mean expression",
                        heatmap_legend_param = list(title_position = "leftcenter-rot",border = TRUE)
                  )

10.10 Plot heatmap of functional genes

functional_genes_to_plot <- c("IDO1","CCL2","CXCL10","CXCL2","CXCL3","IL10","IL18","IL1B","CXCL8","CXCL16","LYZ","IL15","CCL19","CXCL19","TNF","CCL3","CCL4","CCL5","XCL1","XCL2","IFNG","CSF2")

functional_genes_to_plot <- functional_genes_to_plot[functional_genes_to_plot %in% rownames(assay(pb_all))]
fx_mat <- assay(pb_all)[functional_genes_to_plot,cluster_order]
colnames(fx_mat) <- cluster_annot[cluster_order]
ComplexHeatmap::Heatmap(fx_mat,
                        col = circlize::colorRamp2(c(0,3),hcl_palette = "viridis"),
                        cluster_rows = FALSE,
                        cluster_columns = FALSE,
                        column_split = slice_order,
                        cluster_column_slices = FALSE,
                        border = TRUE,
                        row_names_gp = gpar(fontsize=6,fontface="italic"),
                        column_names_gp = gpar(fontsize=8),
                        column_title_gp = gpar(fontsize=8,fontface="bold"),
                        name = "Mean expression",
                        heatmap_legend_param = list(title_position = "leftcenter-rot",border = TRUE)
                  )

11 Run propeller to test for differential abundance between Post3rd and Pre3rd timepoints

Extract transformed cell proportions then run paired test via limma, modeling repeated measures as random effects. Exclude P109 as we only have data from one timepoint

merged.18279.skin.singlets.paired <- subset(merged.18279.skin.singlets, subset = Patient != "P109")
props_transformed <- getTransformedProps(clusters = merged.18279.skin.singlets.paired$sub.cluster,
                                         sample = merged.18279.skin.singlets.paired$Sample,
                                         transform = "logit")
Performing logit transformation of proportions
sample <- levels(as.factor(merged.18279.skin.singlets.paired$Sample))
patient <- str_replace_all(levels(as.factor(merged.18279.skin.singlets.paired$Sample)),"_.+","")
timepoint <- factor(str_replace_all(levels(as.factor(merged.18279.skin.singlets.paired$Sample)),".+Skin_(.+)_.{1,3}mgIpi.+","\\1"), levels = c("Pre3rd","Post3rd"))

# Now model repeated measures as random effects
mm.randomeffects <- model.matrix(~timepoint)
dupcor <- duplicateCorrelation(props_transformed$TransformedProps, 
                                design = mm.randomeffects,
                                block = patient)

fit1 <- lmFit(props_transformed$TransformedProps, 
              design = mm.randomeffects, 
              block = patient, 
              correlation = dupcor$consensus)
fit1 <- eBayes(fit1)
summary(decideTests(fit1))
       (Intercept) timepointPost3rd
Down            37                3
NotSig           0               30
Up               0                4
knitr::kable(topTable(fit1, coef=2, n = Inf),format = "html")
logFC AveExpr t P.Value adj.P.Val B
0 2.0372839 -2.394190 6.0444635 0.0000076 0.0002827 3.7684748
14 1.6722793 -4.717433 4.0237465 0.0007066 0.0130712 -0.6950065
25 -1.8854579 -7.133305 -3.7754513 0.0012507 0.0154247 -1.2514785
26 -1.7251857 -7.277714 -3.6470238 0.0016794 0.0155341 -1.5375864
12 1.0379681 -4.083276 3.3382469 0.0033978 0.0251440 -2.2178147
18 -1.6871969 -5.413646 -2.9924233 0.0073954 0.0454955 -2.9605113
5_2 0.9933008 -5.637332 2.9240214 0.0086073 0.0454955 -3.1042062
16 -1.3803401 -4.692006 -2.7983726 0.0113488 0.0514789 -3.3648310
4_1 1.5185041 -5.091033 2.7533251 0.0125219 0.0514789 -3.4571341
17 -1.8329522 -5.608296 -2.6637661 0.0152063 0.0562632 -3.6387123
8 -1.2519025 -3.302221 -2.5457344 0.0195848 0.0658763 -3.8737898
13_1 1.1374875 -5.460543 2.3393461 0.0302089 0.0875685 -4.2718698
1_1 -0.6599773 -3.410167 -2.3129621 0.0319003 0.0875685 -4.3214553
2_2 0.8171467 -4.803539 2.2945227 0.0331340 0.0875685 -4.3559233
15 1.0173018 -4.894675 2.2529935 0.0360753 0.0889857 -4.4329774
5_0 0.6705836 -3.453656 2.0874039 0.0503389 0.1129548 -4.7318752
23 -0.8396384 -5.751894 -2.0719892 0.0518982 0.1129548 -4.7589859
19_0 -0.6803836 -4.912607 -1.9752811 0.0627123 0.1240649 -4.9261277
7 -0.6539035 -2.918318 -1.9479953 0.0661076 0.1240649 -4.9723400
2_0 -0.5228991 -3.160799 -1.9186934 0.0699347 0.1240649 -5.0214888
21 -1.1218904 -5.249651 -1.9151154 0.0704152 0.1240649 -5.0274560
6_1 -0.6018666 -3.466670 -1.7971350 0.0879797 0.1479658 -5.2199023
24 -1.0070841 -5.843023 -1.6958138 0.1059919 0.1705087 -5.3782061
9_0 -0.4366994 -3.278952 -1.5446546 0.1386678 0.2137795 -5.6015873
5_1 0.4049160 -4.283412 1.2705922 0.2189756 0.3240839 -5.9640433
3 -0.3874227 -2.528013 -1.2220670 0.2363972 0.3364113 -6.0220926
22 0.3036054 -4.998612 0.9330817 0.3623040 0.4964907 -6.3265136
13_0 0.3039308 -4.869004 0.8508420 0.4052891 0.5351919 -6.3996098
6_0 -0.2078642 -3.240736 -0.8249576 0.4194747 0.5351919 -6.4213259
9_1 0.2364163 -5.617484 0.7436740 0.4660307 0.5630970 -6.4854365
2_1 0.2356230 -3.838024 0.7339792 0.4717840 0.5630970 -6.4926657
11_1 -0.1760875 -5.322509 -0.6955733 0.4949864 0.5723280 -6.5204207
4_0 0.1046727 -3.034102 0.3956441 0.6967066 0.7811559 -6.6875096
10 -0.1833153 -4.298277 -0.3186360 0.7534247 0.8199034 -6.7158705
11_0 0.0794545 -3.736810 0.2849193 0.7787406 0.8232400 -6.7263836
20 -0.1197930 -6.124942 -0.1990117 0.8443350 0.8677887 -6.7478958
1_0 -0.0224649 -2.569546 -0.0887565 0.9301899 0.9301899 -6.7643451
# Make table of clusters with significant proportional change
sigProps <- topTable(fit1, coef=2, n = Inf)[topTable(fit1, coef=2, n = Inf)$adj.P.Val < 0.1,] %>%
    rownames_to_column(var = "sub.cluster") %>%
    as_tibble() %>%
    dplyr::select(sub.cluster,adj.P.Val) %>%
    mutate(Annot = cluster_annot[sub.cluster])

sigProps
# A tibble: 15 × 3
   sub.cluster adj.P.Val Annot                  
   <chr>           <dbl> <chr>                  
 1 0            0.000283 MonoMac_1              
 2 14           0.0131   pDC                    
 3 25           0.0154   Macrophages            
 4 26           0.0155   Proliferating_cDC2     
 5 12           0.0251   MonoMac_3              
 6 18           0.0455   cDC1                   
 7 5_2          0.0455   NK_cells               
 8 16           0.0515   Mast                   
 9 4_1          0.0515   CD8_T_Naive/CM_3       
10 17           0.0563   Endothelial            
11 8            0.0659   Keratinocytes          
12 13_1         0.0876   Proliferating_T_cells_2
13 1_1          0.0876   CD4_T_CTL              
14 2_2          0.0876   CD8_T_EM_2             
15 15           0.0890   DC_LAMP3               

11.1 Plot boxplots of cell proportion differences, Pre vs Post, connecting patients with a line

totalsPerSample <- enframe(table(merged.18279.skin.singlets$Sample),name="Sample",value="TotalCells") %>%
    as_tibble() %>%
    mutate(TotalCells = as.numeric(TotalCells)) %>%
    mutate(Sample = str_replace_all(Sample,"_.{1,3}mgIpi_RNA",""))

CellAnnotation.labels <- enframe(cluster_annot,value="CellAnnotation") %>%
  as_tibble() %>%
  left_join(sigProps,by = c("CellAnnotation" = "Annot")) %>%
    mutate(CellAnnotation.labels = case_when(
      adj.P.Val < 0.05 ~ paste0(CellAnnotation,"**\nadj.p = ",round(adj.P.Val,4)),
      (adj.P.Val >= 0.05) & (adj.P.Val < 0.1) ~ paste0(CellAnnotation,"*\nadj.p = ",round(adj.P.Val,4)),
      is.na(adj.P.Val) ~ CellAnnotation
      )
    ) %>%
  dplyr::select(CellAnnotation,CellAnnotation.labels) %>%
  deframe()

rownames_to_column(as.data.frame(merged.18279.skin.singlets@meta.data),var="Barcode") %>%
    as_tibble() %>%
    dplyr::select(Barcode,sub.cluster,Sample,CellAnnotation) %>%
    mutate(Sample = str_replace_all(Sample,"_.{1,3}mgIpi_RNA","")) %>%
    group_by(CellAnnotation,Sample) %>%
    summarize(n = dplyr::n()) %>%
    ungroup() %>%
    complete(Sample,CellAnnotation) %>%             # Makes sure 0s get represented rather than omitted
    mutate(n = replace_na(n,0)) %>%
    mutate(Patient = str_split_i(Sample,pattern = "_",i = 1)) %>%
    mutate(Site = str_split_i(Sample,pattern = "_",i = 2)) %>%
    mutate(Timepoint = str_split_i(Sample,pattern = "_",i = 3)) %>%
    mutate(Timepoint = str_replace_all(Timepoint,"3rd","")) %>%
    right_join(totalsPerSample,.,by="Sample") %>%
      group_by(Sample) %>%
      mutate(Proportion = n / TotalCells) %>%
    left_join(sigProps,by = c("CellAnnotation" = "Annot")) %>%
    mutate(CellAnnotation = factor(CellAnnotation, levels = cluster_annot[cluster_order])) %>%
    ungroup() %>%
    ggplot(aes(x = fct_relevel(Timepoint,c("Pre","Post")),
               y = Proportion,
               fill = fct_relevel(Timepoint,c("Pre","Post"))
              )
           ) +
      geom_boxplot(outlier.shape=NA, width = 0.5) +
    facet_wrap(~CellAnnotation,scales="free_y") +
      lemon::geom_pointpath(aes(group=Patient), 
                            position = position_jitterdodge(jitter.width = 0.1,
                                                            dodge.width = 0.4,
                                                            seed=123)
                            ) +
      facet_wrap(~CellAnnotation,scales="free_y",ncol=6,labeller = as_labeller(CellAnnotation.labels)) +
    theme_bw() +
    theme(axis.text.y = element_text(size=10),
        strip.text = element_text(size=8,face="bold"),
        axis.title = element_text(size=10,face="bold"),
        legend.text = element_text(size=10),
        legend.title = element_text(size=10,face="bold"),
        legend.key.size = unit(2,"line"),
        panel.grid = element_blank(), 
        panel.grid.minor = element_blank(),
        panel.border = element_rect(fill = NA, color = "black")
        ) +
      xlab("Timepoint") +
      ylab("Skin composition fraction") +
    labs(fill = "Timepoint") +
    scale_fill_manual(values=c("lightgreen","darkgreen"))
`summarise()` has grouped output by 'CellAnnotation'. You can override using
the `.groups` argument.

11.2 Plot the same except now facet both by Cluster and Ipilimumab concentration cohort

rownames_to_column(as.data.frame(merged.18279.skin.singlets@meta.data),var="Barcode") %>%
    as_tibble() %>%
    dplyr::select(Barcode,sub.cluster,Sample,CellAnnotation) %>%
    group_by(CellAnnotation,Sample) %>%
    summarize(n = dplyr::n()) %>%
    ungroup() %>%
    complete(Sample,CellAnnotation) %>%             # Makes sure 0s get represented rather than omitted
    mutate(n = replace_na(n,0)) %>%
    mutate(Patient = str_split_i(Sample,pattern = "_",i = 1)) %>%
    mutate(Site = str_split_i(Sample,pattern = "_",i = 2)) %>%
    mutate(Timepoint = str_split_i(Sample,pattern = "_",i = 3)) %>%
    mutate(Timepoint = str_replace_all(Timepoint,"3rd","")) %>%
    mutate(IpiCohort = str_split_i(Sample,pattern = "_",i = 4)) %>%
    mutate(Sample = str_replace_all(Sample,"_.{1,3}mgIpi_RNA",""))  %>%
    right_join(totalsPerSample,.,by="Sample") %>%
      group_by(Sample) %>%
      mutate(Proportion = n / TotalCells) %>%
    left_join(sigProps,by = c("CellAnnotation" = "Annot")) %>%
    mutate(CellAnnotation = factor(CellAnnotation, levels = cluster_annot[cluster_order])) %>%
    ungroup() %>%
    ggplot(aes(x = fct_relevel(Timepoint,c("Pre","Post")),
               y = Proportion,
               fill = fct_relevel(Timepoint,c("Pre","Post"))
              )
           ) +
      geom_boxplot(outlier.shape=NA, width = 0.5) +
    geom_point(aes(color = Patient), position = position_jitterdodge(jitter.width=0.1,dodge.width = 0.4)) +
    facet_grid(CellAnnotation~IpiCohort,scales="free") +
    theme_bw() +
    theme(axis.text.y = element_text(size=10),
        strip.text = element_text(size=8,face="bold"),
        axis.title = element_text(size=10,face="bold"),
        legend.text = element_text(size=10),
        legend.title = element_text(size=10,face="bold"),
        legend.key.size = unit(2,"line"),
        panel.grid = element_blank(), 
        panel.grid.minor = element_blank(),
        panel.border = element_rect(fill = NA, color = "black"),
        axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)
        ) +
      xlab("Timepoint") +
      ylab("Skin composition fraction") +
    labs(fill = "Cohort", color = "Patient") +
    scale_color_manual(values = c("#59A14FFF",  
                                  "#B07AA1FF", 
                                  "#76B7B2FF", 
                                  "#FBB258FF", 
                                  "#DC050CFF", 
                                  "#F6AAC9FF", 
                                  "#5CA2E5FF", 
                                  "#615EBFFF", 
                                  "#826250FF")) +
    scale_fill_manual(values=c("lightgreen","darkgreen"))
`summarise()` has grouped output by 'CellAnnotation'. You can override using
the `.groups` argument.

11.3 Prepare UMAP with clusters of interest circled

umap_circled <- umap %>%
    ggplot(aes(x = umapharmony_1, y = umapharmony_2, color = CellAnnotation)) +
    geom_point(size = 0.25) +
    theme_classic() +
    geom_shape(data = umap %>% dplyr::filter(sub.cluster=="0"), 
               stat = "ellipse", 
               expand = unit(0.25, 'cm'), 
               fill = NA, 
               color = "black", 
               linetype = 2) +
    geom_shape(data = umap %>% dplyr::filter(sub.cluster=="12"), 
               stat = "ellipse", 
               expand = unit(0.25, 'cm'), 
               fill = NA, 
               color = "black", 
               linetype = 2) +
    geom_shape(data = umap %>% dplyr::filter(sub.cluster=="15"), 
               stat = "ellipse", 
               expand = unit(0.25, 'cm'), 
               fill = NA, 
               color = "black", 
               linetype = 2) +
    geom_shape(data = umap %>% dplyr::filter(sub.cluster=="5_2"),
               stat = "ellipse",
               expand = unit(0.25, 'cm'),
               fill = NA,
               color = "black",
               linetype = 2) +
  guides(color = "none")

umap_circled

11.4 For each cluster of interest, plot differentially expressed genes as a heatmap and add annotation layer illustrating changing cell proportions

11.4.1 Create function for centering expression values around mean of Pre3rd samples

controlCenter <- function(data_matrix = NULL, pattern_for_controls = NULL, pattern_for_others = NULL) {
  stopifnot(is.matrix(data_matrix))

    ctrl_means <- apply(data_matrix, 1, function(x) {mean(x[grep(pattern_for_controls,colnames(data_matrix))])})
    data_matrix_centered <- as.data.frame(matrix(data=NA,nrow=length(rownames(data_matrix)),ncol=length(colnames(data_matrix))))
    rownames(data_matrix_centered) <- rownames(data_matrix)
    colnames(data_matrix_centered) <- colnames(data_matrix)[c(grep(pattern_for_controls,colnames(data_matrix)),grep(pattern_for_others,colnames(data_matrix)))]

    for (i in 1:length(ctrl_means)) {
      data_matrix_centered[i,] <- as.numeric(data_matrix[i,c(grep(pattern_for_controls,colnames(data_matrix)),grep(pattern_for_others,colnames(data_matrix)))]) - as.numeric(ctrl_means[i])
    }
  return <- data_matrix_centered
  
}

11.4.2 Create pseudobulks for plotting

Use mean (rather than sum) to control for varying number of cells

(mergedCondition.sce <- prepSCE(merged.sce,
        kid = "sub.cluster",
        gid = "Timepoint",
        sid = "Sample",
        drop = TRUE))
class: SingleCellExperiment 
dim: 61217 33397 
metadata(1): experiment_info
assays(2): counts logcounts
rownames(61217): 5-8S-rRNA 5S-rRNA ... ZZEF1 ZZZ3
rowData names(0):
colnames(33397): P101_Skin_Pre3rd_2.5mgIpi_RNA_GCACATAAGTGCCATT
  P101_Skin_Pre3rd_2.5mgIpi_RNA_GGAACTTGTCGCGGTT ...
  P111_Skin_Post3rd_5mgIpi_RNA_ACGATGTTCCTAGGGC
  P111_Skin_Post3rd_5mgIpi_RNA_GAACATCAGCCACGTC
colData names(3): cluster_id sample_id group_id
reducedDimNames(4): PCA UMAP.UNINTEGRATED INTEGRATED.HARMONY
  UMAP.HARMONY
mainExpName: RNA
altExpNames(0):
pb <- aggregateData(mergedCondition.sce,
    assay = "counts",
    fun = "mean",
    by = c("cluster_id", "sample_id"))

11.4.3 Create cell proportion table per Sample and per sub-cluster

props <- rownames_to_column(as.data.frame(merged.18279.skin.singlets@meta.data),var="Barcode") %>%
    as_tibble() %>%
    dplyr::select(Barcode,CellAnnotation,Sample) %>%
    mutate(Sample = str_replace_all(Sample,"_.{1,3}mgIpi_RNA","")) %>%
    group_by(CellAnnotation,Sample) %>%
    summarize(n = dplyr::n()) %>%
    ungroup() %>%
    complete(Sample,CellAnnotation) %>%             # Makes sure 0s get represented rather than omitted
    mutate(n = replace_na(n,0)) %>%
    mutate(Patient = str_split_i(Sample,pattern = "_",i = 1)) %>%
    mutate(Site = str_split_i(Sample,pattern = "_",i = 2)) %>%
    mutate(Timepoint = str_split_i(Sample,pattern = "_",i = 3)) %>%
    mutate(Timepoint = str_replace_all(Timepoint,"3rd","")) %>%
    right_join(totalsPerSample,.,by="Sample") %>%
    mutate(Sample = str_replace_all(Sample,"_Skin","")) %>%
    mutate(Sample = str_replace_all(Sample,"3rd","")) %>%
      group_by(Sample) %>%
      mutate(Proportion = n / TotalCells)
`summarise()` has grouped output by 'CellAnnotation'. You can override using
the `.groups` argument.
props
# A tibble: 629 × 8
# Groups:   Sample [17]
   Sample    TotalCells CellAnnotation      n Patient Site  Timepoint Proportion
   <chr>          <dbl> <fct>           <int> <chr>   <chr> <chr>          <dbl>
 1 P101_Post       1718 MonoMac_1         410 P101    Skin  Post        0.239   
 2 P101_Post       1718 CD4_T_Naive/CM     74 P101    Skin  Post        0.0431  
 3 P101_Post       1718 CD4_T_CTL          15 P101    Skin  Post        0.00873 
 4 P101_Post       1718 CD8_T_EM_1         70 P101    Skin  Post        0.0407  
 5 P101_Post       1718 CD8_T_Naive/CM…    25 P101    Skin  Post        0.0146  
 6 P101_Post       1718 CD8_T_EM_2         18 P101    Skin  Post        0.0105  
 7 P101_Post       1718 MonoMac_2         264 P101    Skin  Post        0.154   
 8 P101_Post       1718 CD8_T_Naive/CM…    80 P101    Skin  Post        0.0466  
 9 P101_Post       1718 CD8_T_Naive/CM…     1 P101    Skin  Post        0.000582
10 P101_Post       1718 NK/T_1             18 P101    Skin  Post        0.0105  
# ℹ 619 more rows

11.4.4 Cluster 0

# Subset to genes of interest and reorder columns
cluster <- "0"
goi <- c("IL6","TNF","CXCL10","CXCL11","CCL2","CCL3","CCL4","CCL5","CCL8","IL15","SELL")
pb_cluster <- assays(pb)[[cluster]][goi,]
pb_cluster_cnames <- str_replace_all(str_replace_all(colnames(pb_cluster),"3rd_.{1,3}mgIpi_RNA",""),"_Skin","")
colnames(pb_cluster) <- pb_cluster_cnames
pb_cluster_cnames_sorted <- pb_cluster_cnames[c(which(grepl("Pre",pb_cluster_cnames)),which(grepl("Post",pb_cluster_cnames)))]

# Subset proportions table
cluster_props <- props %>%
  dplyr::filter(CellAnnotation == cluster_annot[cluster]) %>%
  dplyr::select(Sample,Proportion) %>%
  ungroup() %>%
  mutate(Sample = fct_relevel(as.factor(Sample),pb_cluster_cnames_sorted)) %>%
  dplyr::arrange(Sample) %>%
  as.data.frame()

# Center expression around mean of Pre samples
pb_centered <- controlCenter(pb_cluster[,pb_cluster_cnames_sorted], pattern_for_controls = "Pre", pattern_for_others = "Post")

# Plot heatmap
timepoints <- as.factor(str_split_i(pb_cluster_cnames_sorted,"_",2))
ha1 <- HeatmapAnnotation(Timepoint = timepoints,
                         show_legend = FALSE,
                         col = list(Timepoint = setNames(c("lightgreen", "darkgreen"), c("Pre", "Post"))), 
                         border = TRUE)
ha2 <- HeatmapAnnotation(Proportion = anno_barplot(round(cluster_props$Proportion,3), 
                                                   gp = gpar(fill = c(
                                                      rep("lightgreen",length(which(str_detect(cluster_props$Sample,"Pre")))), 
                                                      rep("darkgreen",length(which(str_detect(cluster_props$Sample,"Post"))))
                                                      )
                                                     ), 
                                                   add_numbers = TRUE,
                                                   numbers_rot = 0
                                                   )
                         )

p0 <- ComplexHeatmap::Heatmap(pb_centered,
                        cluster_rows = FALSE,
                        cluster_columns = FALSE,
                        column_split = factor(timepoints,levels = c("Pre","Post")),
                        column_labels = str_replace_all(colnames(pb_centered),"_Pre|_Post",""),
                        row_names_gp = gpar(fontface = "italic",fontsize = 8),
                        column_names_gp = gpar(fontsize = 8),
                        col = colorRamp2(c(0,5),hcl_palette = "viridis"),
                        border = TRUE,
                        column_title = paste0("Cluster ", cluster, ": ",cluster_annot[cluster]),
                        column_title_gp = gpar(fontface = "bold"),
                        name = "Expression relative to Pre",
                        top_annotation = c(ha1,ha2),
                        show_heatmap_legend = FALSE
) %>% 
  draw() %>% 
  grid.grabExpr()
Warning: The input is a data frame-like object, convert it to a matrix.

11.4.5 Cluster 12

# Subset to genes of interest and reorder columns
cluster <- "12"
goi <- c("CXCL10","CXCL9","CXCL11","CCL8","IL18","IL6","TNF")
pb_cluster <- assays(pb)[[cluster]][goi,]
pb_cluster_cnames <- str_replace_all(str_replace_all(colnames(pb_cluster),"3rd_.{1,3}mgIpi_RNA",""),"_Skin","")
colnames(pb_cluster) <- pb_cluster_cnames
pb_cluster_cnames_sorted <- pb_cluster_cnames[c(which(grepl("Pre",pb_cluster_cnames)),which(grepl("Post",pb_cluster_cnames)))]

# Subset proportions table
cluster_props <- props %>%
  dplyr::filter(CellAnnotation == cluster_annot[cluster]) %>%
  dplyr::select(Sample,Proportion) %>%
  ungroup() %>%
  mutate(Sample = fct_relevel(as.factor(Sample),pb_cluster_cnames_sorted)) %>%
  dplyr::arrange(Sample) %>%
  as.data.frame()

# Center expression around mean of Pre samples
pb_centered <- controlCenter(pb_cluster[,pb_cluster_cnames_sorted], pattern_for_controls = "Pre", pattern_for_others = "Post")

# Plot heatmap
timepoints <- as.factor(str_split_i(pb_cluster_cnames_sorted,"_",2))
ha1 <- HeatmapAnnotation(Timepoint = timepoints,
                         show_legend = FALSE,
                         col = list(Timepoint = setNames(c("lightgreen", "darkgreen"), c("Pre", "Post"))), 
                         border = TRUE)
ha2 <- HeatmapAnnotation(Proportion = anno_barplot(round(cluster_props$Proportion,3), 
                                                   gp = gpar(fill = c(
                                                      rep("lightgreen",length(which(str_detect(cluster_props$Sample,"Pre")))), 
                                                      rep("darkgreen",length(which(str_detect(cluster_props$Sample,"Post"))))
                                                      )
                                                     ),
                                                   add_numbers = TRUE,
                                                   numbers_rot = 0
                                                   )
                         )

p12 <- ComplexHeatmap::Heatmap(pb_centered,
                        cluster_rows = FALSE,
                        cluster_columns = FALSE,
                        column_split = factor(timepoints,levels = c("Pre","Post")),
                        column_labels = str_replace_all(colnames(pb_centered),"_Pre|_Post",""),
                        row_names_gp = gpar(fontface = "italic",fontsize = 8),
                        column_names_gp = gpar(fontsize = 8),
                        col = colorRamp2(c(0,5),hcl_palette = "viridis"),
                        border = TRUE,
                        column_title = paste0("Cluster ", cluster, ": ", cluster_annot[cluster]),
                        column_title_gp = gpar(fontface = "bold"),
                        name = "Expression relative to Pre",
                        top_annotation = c(ha1,ha2),
                        show_heatmap_legend = FALSE
) %>% 
  draw() %>% 
  grid.grabExpr()
Warning: The input is a data frame-like object, convert it to a matrix.

11.4.6 Cluster 15

# Subset to genes of interest and reorder columns
cluster <- "15"
goi <- c("CCL19","CD40","CD86","CXCL9")
pb_cluster <- assays(pb)[[cluster]][goi,]
pb_cluster_cnames <- str_replace_all(str_replace_all(colnames(pb_cluster),"3rd_.{1,3}mgIpi_RNA",""),"_Skin","")
colnames(pb_cluster) <- pb_cluster_cnames
pb_cluster_cnames_sorted <- pb_cluster_cnames[c(which(grepl("Pre",pb_cluster_cnames)),which(grepl("Post",pb_cluster_cnames)))]

# Subset proportions table
cluster_props <- props %>%
  dplyr::filter(CellAnnotation == cluster_annot[cluster]) %>%
  dplyr::select(Sample,Proportion) %>%
  ungroup() %>%
  mutate(Sample = fct_relevel(as.factor(Sample),pb_cluster_cnames_sorted)) %>%
  dplyr::arrange(Sample) %>%
  as.data.frame()

# Center expression around mean of Pre samples
pb_centered <- controlCenter(pb_cluster[,pb_cluster_cnames_sorted], pattern_for_controls = "Pre", pattern_for_others = "Post")

# Plot heatmap
timepoints <- as.factor(str_split_i(pb_cluster_cnames_sorted,"_",2))
ha1 <- HeatmapAnnotation(Timepoint = timepoints,
                         show_legend = FALSE,
                         col = list(Timepoint = setNames(c("lightgreen", "darkgreen"), c("Pre", "Post"))), 
                         border = TRUE)
ha2 <- HeatmapAnnotation(Proportion = anno_barplot(round(cluster_props$Proportion,3), 
                                                   gp = gpar(fill = c(
                                                      rep("lightgreen",length(which(str_detect(cluster_props$Sample,"Pre")))), 
                                                      rep("darkgreen",length(which(str_detect(cluster_props$Sample,"Post"))))
                                                      )
                                                     ),
                                                   add_numbers = TRUE,
                                                   numbers_rot = 0
                                                   )
                         )

p15 <- ComplexHeatmap::Heatmap(pb_centered,
                        cluster_rows = FALSE,
                        cluster_columns = FALSE,
                        column_split = factor(timepoints,levels = c("Pre","Post")),
                        column_labels = str_replace_all(colnames(pb_centered),"_Pre|_Post",""),
                        row_names_gp = gpar(fontface = "italic",fontsize = 8),
                        column_names_gp = gpar(fontsize = 8),
                        col = colorRamp2(c(0,5),hcl_palette = "viridis"),
                        border = TRUE,
                        column_title = paste0("Cluster ", cluster, ": ",cluster_annot[cluster]),
                        column_title_gp = gpar(fontface = "bold"),
                        name = "Expression relative to Pre",
                        top_annotation = c(ha1,ha2),
                        show_heatmap_legend = FALSE
) %>% 
  draw() %>% 
  grid.grabExpr()
Warning: The input is a data frame-like object, convert it to a matrix.

11.4.7 Cluster 5_2

# Subset to genes of interest and reorder columns
cluster <- "5_2"
goi <- c("GNLY","GZMB","PRF1")
pb_cluster <- assays(pb)[[cluster]][goi,]
pb_cluster_cnames <- str_replace_all(str_replace_all(colnames(pb_cluster),"3rd_.{1,3}mgIpi_RNA",""),"_Skin","")
colnames(pb_cluster) <- pb_cluster_cnames
pb_cluster_cnames_sorted <- pb_cluster_cnames[c(which(grepl("Pre",pb_cluster_cnames)),which(grepl("Post",pb_cluster_cnames)))]

# Subset proportions table
cluster_props <- props %>%
  dplyr::filter(CellAnnotation == cluster_annot[cluster]) %>%
  dplyr::select(Sample,Proportion) %>%
  ungroup() %>%
  mutate(Sample = fct_relevel(as.factor(Sample),pb_cluster_cnames_sorted)) %>%
  dplyr::arrange(Sample) %>%
  as.data.frame()

# Center expression around mean of Pre samples
pb_centered <- controlCenter(pb_cluster[,pb_cluster_cnames_sorted], pattern_for_controls = "Pre", pattern_for_others = "Post")

# Plot heatmap
timepoints <- as.factor(str_split_i(pb_cluster_cnames_sorted,"_",2))
ha1 <- HeatmapAnnotation(Timepoint = timepoints,
                         show_legend = FALSE,
                         col = list(Timepoint = setNames(c("lightgreen", "darkgreen"), c("Pre", "Post"))), 
                         border = TRUE)
ha2 <- HeatmapAnnotation(Proportion = anno_barplot(round(cluster_props$Proportion,3), 
                                                   gp = gpar(fill = c(
                                                      rep("lightgreen",length(which(str_detect(cluster_props$Sample,"Pre")))), 
                                                      rep("darkgreen",length(which(str_detect(cluster_props$Sample,"Post"))))
                                                      )
                                                     ),
                                                   add_numbers = TRUE,
                                                   numbers_rot = 0
                                                   )
                         )

p5_2 <- ComplexHeatmap::Heatmap(pb_centered,
                        cluster_rows = FALSE,
                        cluster_columns = FALSE,
                        column_split = factor(timepoints,levels = c("Pre","Post")),
                        column_labels = str_replace_all(colnames(pb_centered),"_Pre|_Post",""),
                        row_names_gp = gpar(fontface = "italic",fontsize = 8),
                        column_names_gp = gpar(fontsize = 8),
                        col = colorRamp2(c(0,5),hcl_palette = "viridis"),
                        border = TRUE,
                        column_title = paste0("Cluster ", cluster, ": ",cluster_annot[cluster]),
                        column_title_gp = gpar(fontface = "bold"),
                        name = "Expression relative to Pre",
                        top_annotation = c(ha1,ha2),
                        show_heatmap_legend = FALSE
) %>% 
  draw() %>% 
  grid.grabExpr()
Warning: The input is a data frame-like object, convert it to a matrix.

11.5 Assemble final figure

wrap_plots(A = p0, 
           B = p12, 
           C = p15, 
           D = p5_2,
           U = umap_circled,
            design = "C##A
                      DUUB
                      #UU#")

11.6 Get session info

sessionInfo()
R version 4.3.1 (2023-06-16)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Rocky Linux 8.10 (Green Obsidian)

Matrix products: default
BLAS/LAPACK: /usr/lib64/libopenblasp-r0.3.15.so;  LAPACK version 3.9.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: America/New_York
tzcode source: system (glibc)

attached base packages:
[1] stats4    grid      stats     graphics  grDevices utils     datasets 
[8] methods   base     

other attached packages:
 [1] knitr_1.45                  speckle_1.0.0              
 [3] cowplot_1.1.3               ggforce_0.4.2              
 [5] lemon_0.4.9                 scuttle_1.10.3             
 [7] limma_3.56.2                muscat_1.14.0              
 [9] SingleCellExperiment_1.22.0 SummarizedExperiment_1.30.2
[11] Biobase_2.60.0              GenomicRanges_1.52.1       
[13] GenomeInfoDb_1.36.4         IRanges_2.34.1             
[15] S4Vectors_0.38.2            BiocGenerics_0.46.0        
[17] MatrixGenerics_1.12.3       matrixStats_1.2.0          
[19] circlize_0.4.15             ComplexHeatmap_2.16.0      
[21] msigdbr_7.5.1               paletteer_1.6.0            
[23] patchwork_1.3.0             lubridate_1.9.3            
[25] forcats_1.0.0               stringr_1.5.1              
[27] dplyr_1.1.4                 purrr_1.0.2                
[29] readr_2.1.5                 tidyr_1.3.1                
[31] tibble_3.2.1                tidyverse_2.0.0            
[33] Seurat_5.1.0                SeuratObject_5.0.2         
[35] sp_2.1-3                    ggplot2_3.5.1              
[37] presto_1.0.0                data.table_1.15.0          
[39] Rcpp_1.0.12                

loaded via a namespace (and not attached):
  [1] spatstat.sparse_3.0-3     bitops_1.0-7             
  [3] httr_1.4.7                RColorBrewer_1.1-3       
  [5] doParallel_1.0.17         numDeriv_2016.8-1.1      
  [7] backports_1.4.1           tools_4.3.1              
  [9] sctransform_0.4.1         utf8_1.2.4               
 [11] R6_2.5.1                  lazyeval_0.2.2           
 [13] uwot_0.1.16               mgcv_1.9-1               
 [15] GetoptLong_1.0.5          withr_3.0.0              
 [17] prettyunits_1.2.0         gridExtra_2.3            
 [19] SeuratWrappers_0.3.19     progressr_0.14.0         
 [21] cli_3.6.2                 Cairo_1.6-2              
 [23] spatstat.explore_3.2-6    fastDummies_1.7.3        
 [25] sandwich_3.1-0            labeling_0.4.3           
 [27] mvtnorm_1.2-4             spatstat.data_3.0-4      
 [29] blme_1.0-5                ggridges_0.5.6           
 [31] pbapply_1.7-2             R.utils_2.12.3           
 [33] scater_1.28.0             parallelly_1.37.0        
 [35] generics_0.1.3            shape_1.4.6              
 [37] gtools_3.9.5              ica_1.0-3                
 [39] spatstat.random_3.2-2     Matrix_1.6-4             
 [41] ggbeeswarm_0.7.2          fansi_1.0.6              
 [43] abind_1.4-5               R.methodsS3_1.8.2        
 [45] lifecycle_1.0.4           multcomp_1.4-25          
 [47] yaml_2.3.8                edgeR_3.42.4             
 [49] gplots_3.1.3.1            Rtsne_0.17               
 [51] promises_1.2.1            crayon_1.5.2             
 [53] miniUI_0.1.1.1            lattice_0.22-5           
 [55] beachmat_2.16.0           magick_2.8.3             
 [57] pillar_1.9.0              rjson_0.2.21             
 [59] boot_1.3-29               estimability_1.4.1       
 [61] future.apply_1.11.1       codetools_0.2-19         
 [63] leiden_0.4.3.1            glue_1.7.0               
 [65] remotes_2.4.2.1           vctrs_0.6.5              
 [67] png_0.1-8                 spam_2.10-0              
 [69] Rdpack_2.6                gtable_0.3.4             
 [71] rematch2_2.1.2            xfun_0.42                
 [73] rbibutils_2.2.16          S4Arrays_1.2.0           
 [75] mime_0.12                 coda_0.19-4.1            
 [77] reformulas_0.4.0          survival_3.5-8           
 [79] iterators_1.0.14          statmod_1.5.0            
 [81] ellipsis_0.3.2            fitdistrplus_1.1-11      
 [83] TH.data_1.1-2             ROCR_1.0-11              
 [85] nlme_3.1-164              pbkrtest_0.5.2           
 [87] EnvStats_2.8.1            progress_1.2.3           
 [89] RcppAnnoy_0.0.22          TMB_1.9.10               
 [91] irlba_2.3.5.1             vipor_0.4.7              
 [93] KernSmooth_2.23-22        colorspace_2.1-0         
 [95] nnet_7.3-19               DESeq2_1.40.2            
 [97] tidyselect_1.2.0          emmeans_1.10.0           
 [99] compiler_4.3.1            BiocNeighbors_1.18.0     
[101] DelayedArray_0.26.7       plotly_4.10.4            
[103] caTools_1.18.2            scales_1.3.0             
[105] remaCor_0.0.18            lmtest_0.9-40            
[107] digest_0.6.34             goftest_1.2-3            
[109] spatstat.utils_3.0-4      minqa_1.2.6              
[111] variancePartition_1.30.2  rmarkdown_2.25           
[113] aod_1.3.3                 RhpcBLASctl_0.23-42      
[115] XVector_0.40.0            htmltools_0.5.7          
[117] pkgconfig_2.0.3           lme4_1.1-35.1            
[119] sparseMatrixStats_1.12.2  highr_0.10               
[121] fastmap_1.1.1             rlang_1.1.3              
[123] GlobalOptions_0.1.2       htmlwidgets_1.6.4        
[125] shiny_1.8.0               DelayedMatrixStats_1.22.6
[127] farver_2.1.1              zoo_1.8-12               
[129] jsonlite_1.8.8            BiocParallel_1.34.2      
[131] R.oo_1.26.0               BiocSingular_1.16.0      
[133] RCurl_1.98-1.14           magrittr_2.0.3           
[135] modeltools_0.2-23         GenomeInfoDbData_1.2.10  
[137] dotCall64_1.1-1           munsell_0.5.0            
[139] viridis_0.6.5             babelgene_22.9           
[141] reticulate_1.35.0         stringi_1.8.3            
[143] zlibbioc_1.46.0           MASS_7.3-60.0.1          
[145] flexmix_2.3-19            plyr_1.8.9               
[147] parallel_4.3.1            listenv_0.9.1            
[149] ggrepel_0.9.5             deldir_2.0-2             
[151] splines_4.3.1             tensor_1.5               
[153] hms_1.1.3                 locfit_1.5-9.8           
[155] igraph_2.0.2              spatstat.geom_3.2-8      
[157] RcppHNSW_0.6.0            ScaledMatrix_1.8.1       
[159] reshape2_1.4.4            evaluate_0.23            
[161] BiocManager_1.30.22       tweenr_2.0.2             
[163] nloptr_2.0.3              tzdb_0.4.0               
[165] foreach_1.5.2             httpuv_1.6.14            
[167] RANN_2.6.1                polyclip_1.10-6          
[169] future_1.33.1             clue_0.3-65              
[171] scattermore_1.2           rsvd_1.0.5               
[173] broom_1.0.5               xtable_1.8-4             
[175] RSpectra_0.16-1           later_1.3.2              
[177] viridisLite_0.4.2         lmerTest_3.1-3           
[179] glmmTMB_1.1.10            beeswarm_0.4.0           
[181] cluster_2.1.6             timechange_0.3.0         
[183] globals_0.16.2