Practise Seurat for single cell data analysis

Practise scRNA data analysis with Genome Medicine using Seurat package.
seurat
Author
Published

Monday, November 27, 2023

Modified

Wednesday, January 10, 2024

Packages

# Load packages
library(Seurat)
## Loading required package: SeuratObject
## Loading required package: sp
## 
## Attaching package: 'SeuratObject'
## The following object is masked from 'package:base':
## 
##     intersect
library(harmony)
## Loading required package: Rcpp
library(Matrix)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ tidyr::expand() masks Matrix::expand()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ✖ tidyr::pack()   masks Matrix::pack()
## ✖ tidyr::unpack() masks Matrix::unpack()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(here)
## here() starts at /Users/zhonggr/Library/CloudStorage/OneDrive-Personal/quarto
library(httpgd)

Retrieve data

# Change working directory
setwd("./learn/2023_scRNA_Seurat/data/GSE154778_RAW/")
# getwd()
# Check files
fs <- list.files("./", "^GSM")
# Get the sample names
samples <- str_split(fs, "_", simplify = TRUE)[, 2]
unique(samples)
# Create folders for each sample, and rename
lapply(
  unique(samples), function(x) {
    y  <- fs[grepl(x, fs)]
    folder <- paste(str_split(y[1], "_", simplify = TRUE)[, 2], collapse = "")
    dir.create(folder, recursive = TRUE)
    file.rename(y[1], file.path(folder, "barcodes.tsv.gz"))
    # Note the seurat version to check features.tsv.gz or genes.tsv.gz
    file.rename(y[2], file.path(folder, "features.tsv.gz")) 
    file.rename(y[3], file.path(folder, "matrix.mtx.gz"))
  }
)

Load batch data

# Change working directory
setwd("./learn/2023_scRNA_Seurat/data/GSE154778_RAW/")
folders <- list.files("./")
folders
sceList <- lapply(
  folders, function(folder) {
    CreateSeuratObject(counts = Read10X(folder), project = folder)
  }
)

Merage samples data

Directly merge

# Use Seurat merge
sce.all <- merge(
  x = sceList[[1]],
  y = c(sceList[[2]], sceList[[3]], sceList[[4]], sceList[[5]], sceList[[6]], 
  sceList[[7]], sceList[[8]], sceList[[9]], sceList[[10]], sceList[[11]], 
  sceList[[12]], sceList[[13]], sceList[[14]], sceList[[5]], sceList[[16]]),
  ## Sample names
  add.cell.ids = folders,  
  project = "scRNA"
)
saveRDS(sce.all, here("learn", "2023_scRNA_Seurat", "sce.all.rds"))

Filter

sce.all <- readRDS(here("learn", "2023_scRNA_Seurat", "sce.all.rds"))
head(sce.all@meta.data)
##                         orig.ident nCount_RNA nFeature_RNA
## K16733_AAACATACTCGTTT-1     K16733       2464          965
## K16733_AAACCGTGGGTAGG-1     K16733        689          336
## K16733_AAAGCAGAACGTTG-1     K16733       7145         1919
## K16733_AAAGCAGACTGAGT-1     K16733       1655          621
## K16733_AAAGGCCTGCTCCT-1     K16733      14272         2771
## K16733_AAATACTGTGGATC-1     K16733      13832         2541
table(sce.all@meta.data$orig.ident)
## 
## K16733    T10     T2     T3     T4     T5     T6     T8     T9 Y00006 Y00008 
##    585   1570    837   1026   1826    769   1098   1139    898    786    533 
## Y00013 Y00014 Y00016 Y00027 
##    745    526    272   2484
# Mitochandrial genes
sce.all[["percent.mt"]] <- PercentageFeatureSet(sce.all, pattern = "^MT-")
# Ribonucleoprotein 
sce.all[["percent.rp"]] <- PercentageFeatureSet(sce.all, pattern = "^RP")

# Specific gene set
HB.genes <- c("HBA1","HBA2","HBB","HBD","HBE1","HBG1","HBG2","HBM","HBQ1","HBZ") 
# Red blood cells genes
sce.all[["percent.HB"]]<-PercentageFeatureSet(sce.all, features = HB.genes)

head(sce.all@meta.data)
##                         orig.ident nCount_RNA nFeature_RNA percent.mt
## K16733_AAACATACTCGTTT-1     K16733       2464          965 12.6623377
## K16733_AAACCGTGGGTAGG-1     K16733        689          336  2.1770682
## K16733_AAAGCAGAACGTTG-1     K16733       7145         1919  2.4492652
## K16733_AAAGCAGACTGAGT-1     K16733       1655          621  2.1752266
## K16733_AAAGGCCTGCTCCT-1     K16733      14272         2771  1.5414798
## K16733_AAATACTGTGGATC-1     K16733      13832         2541  0.3181029
##                         percent.rp percent.HB
## K16733_AAACATACTCGTTT-1   13.35227          0
## K16733_AAACCGTGGGTAGG-1   29.02758          0
## K16733_AAAGCAGAACGTTG-1   36.72498          0
## K16733_AAAGCAGACTGAGT-1   35.52870          0
## K16733_AAAGGCCTGCTCCT-1   38.81026          0
## K16733_AAATACTGTGGATC-1   40.73164          0
sce <- subset(
    sce.all, subset = nFeature_RNA > 500 &
        nFeature_RNA < 5000 & percent.mt < 30
)
VlnPlot(sce, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3)

Seurat workflow

sce <- NormalizeData(sce)
## Normalizing layer: counts.1
## Normalizing layer: counts.2
## Normalizing layer: counts.3
## Normalizing layer: counts.4
## Normalizing layer: counts.5
## Normalizing layer: counts.6
## Normalizing layer: counts.7
## Normalizing layer: counts.8
## Normalizing layer: counts.9
## Normalizing layer: counts.10
## Normalizing layer: counts.11
## Normalizing layer: counts.12
## Normalizing layer: counts.13
## Normalizing layer: counts.14
## Normalizing layer: counts.15
## Normalizing layer: counts.16
sce <- FindVariableFeatures(sce, selection.method = "vst", nfeatures = 2000)
## Finding variable features for layer counts.1
## Finding variable features for layer counts.2
## Finding variable features for layer counts.3
## Finding variable features for layer counts.4
## Finding variable features for layer counts.5
## Finding variable features for layer counts.6
## Finding variable features for layer counts.7
## Finding variable features for layer counts.8
## Finding variable features for layer counts.9
## Finding variable features for layer counts.10
## Finding variable features for layer counts.11
## Finding variable features for layer counts.12
## Finding variable features for layer counts.13
## Finding variable features for layer counts.14
## Finding variable features for layer counts.15
## Finding variable features for layer counts.16
all.genes <- rownames(sce)
sce <- ScaleData(sce, features = all.genes)
## Centering and scaling data matrix
sce <- RunPCA(sce, npcs = 50)
## PC_ 1 
## Positive:  KRT19, KRT8, KRT18, SMIM22, MAL2, SPINT2, TSPAN8, CLDN4, GPRC5A, PERP 
##     C19orf33, ELF3, TM4SF1, TMC5, LSR, LGALS4, NQO1, TACSTD2, CLDN7, SPINK1 
##     MUC1, C12orf75, GPX2, TSPAN1, ERBB3, SFTA2, MMP7, CYP3A5, CDH1, TMPRSS4 
## Negative:  VIM, COL1A2, BGN, COL1A1, SERPINF1, FN1, C1R, MGP, CTHRC1, TAGLN 
##     PMP22, NUPR1, THY1, FBLN1, RARRES2, TIMP3, MXRA8, TCF4, CLEC11A, INHBA 
##     RAB31, CCDC80, ASPN, THBS2, APOD, ISLR, TUBA1A, FSTL1, ANTXR1, MEG3 
## PC_ 2 
## Positive:  LAPTM5, AIF1, SRGN, LST1, HLA-DPA1, HLA-DRA, HLA-DPB1, MS4A6A, HLA-DQA1, MS4A7 
##     HLA-DQB1, C1orf162, OLR1, HLA-DRB1, CD53, CD74, CYBB, FCGR2A, CLEC7A, ALOX5AP 
##     CD37, ITGB2, CD14, CD83, MS4A4A, IFI30, RGS1, RNASE6, CD86, HLA-DQA2 
## Negative:  BGN, C1R, TPM1, COL1A1, RARRES2, COL1A2, NBL1, MXRA8, CTHRC1, FSTL1 
##     THY1, FBLN1, CCDC80, IGFBP4, TAGLN, NNMT, THBS2, MGP, ASPN, TIMP3 
##     ISLR, MEG3, APOD, EFEMP2, MFGE8, DKK3, SPON2, FBN1, ANTXR1, EMILIN1 
## PC_ 3 
## Positive:  CTSE, VSIG2, AGR3, FOS, MUC5AC, CYSTM1, ATF3, JUN, FOSB, ELF3 
##     RHOB, LINC01133, IER3, CAPN8, NEAT1, KLF6, TFF3, MUC1, DUSP1, EGR1 
##     BACE2, PIGR, KLF4, KLF2, CREB3L1, REG4, EDN1, HSPA1B, PLAC8, ZG16B 
## Negative:  TUBA1B, TOP2A, MKI67, PTTG1, UBE2C, H2AFZ, CENPW, TPX2, HMGB2, CDK1 
##     STMN1, RRM2, RBP1, ASPM, KLK6, PRC1, HMGB1, ATAD2, NUSAP1, GTSE1 
##     CDKN3, KIF20B, CEP55, HMMR, DTYMK, CDCA3, CLSPN, CENPU, CCNB1, UBE2T 
## PC_ 4 
## Positive:  MDK, TMEM176B, COL11A1, NBL1, TMEM176A, FAM3C, LYZ, INHBA, THBS2, C1QTNF3 
##     GCNT3, GPNMB, FBLN1, KLK6, RARRES2, IGFL2, COL8A1, C12orf75, FNDC1, MMP7 
##     GREM1, PERP, NTM, CLDN3, GJB2, COMP, ISLR, CXCL14, MEG3, RBP1 
## Negative:  PLVAP, RAMP2, VWF, ECSCR, AQP1, CDH5, CALCRL, BCAM, RAMP3, NOTCH4 
##     CLDN5, MMRN2, FAM167B, ADAMTS9, EMCN, CD34, CD93, STC1, CYYR1, GPR4 
##     S1PR1, ANGPT2, PODXL, MYCT1, ARHGAP29, RGS5, CRIP2, ROBO4, GJA4, HIGD1B 
## PC_ 5 
## Positive:  TOP2A, UBE2C, ATAD2, NUCKS1, PRC1, STMN1, SLPI, GTSE1, CENPW, HMGB1 
##     TPX2, CTSD, RAD51AP1, PLAT, CDK1, RRM2, DTYMK, CLSPN, ANLN, MYBL2 
##     FAM83A, CCNB1, NUSAP1, CENPU, ASPM, UBE2T, PRR11, MKI67, CEP55, CRIP2 
## Negative:  CD3D, CD2, PTPRCAP, CD7, C12orf75, CCL5, CD3G, RBP1, GCNT3, CD27 
##     KLRB1, LTB, ZFAS1, CA12, GZMM, RHOH, SPINK1, VNN1, ICOS, CLDN3 
##     CYTIP, TIGIT, PTPN7, GZMB, CDHR2, RUNX3, SLC7A11, CCR7, CD8A, FAM3C
sce <- FindNeighbors(sce, dims = 1:30)
## Computing nearest neighbor graph
## Computing SNN
sce <- FindClusters(sce, resolution = 0.5)
## Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
## 
## Number of nodes: 11914
## Number of edges: 436451
## 
## Running Louvain algorithm...
## Maximum modularity in 10 random starts: 0.9419
## Number of communities: 18
## Elapsed time: 1 seconds
sce <- RunUMAP(sce, dims = 1:30)
## 15:18:03 UMAP embedding parameters a = 0.9922 b = 1.112
## 15:18:03 Read 11914 rows and found 30 numeric columns
## 15:18:03 Using Annoy for neighbor search, n_neighbors = 30
## 15:18:03 Building Annoy index with metric = cosine, n_trees = 50
## 0%   10   20   30   40   50   60   70   80   90   100%
## [----|----|----|----|----|----|----|----|----|----|
## **************************************************|
## 15:18:03 Writing NN index file to temp file /var/folders/2c/9q3pg2295195bp3gnrgbzrg40000gn/T//RtmpjzstFL/file2afb9bffbb
## 15:18:03 Searching Annoy index using 1 thread, search_k = 3000
## 15:18:05 Annoy recall = 100%
## 15:18:05 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 30
## 15:18:06 Initializing from normalized Laplacian + noise (using RSpectra)
## 15:18:06 Commencing optimization for 200 epochs, with 492128 positive edges
## 15:18:10 Optimization finished
# sce <- RunTSNE(sce, dims = 1:30)

UMAP

# Rename sample
sce@meta.data$sample[sce@meta.data$orig.ident == "K16733"] <- "P01"
sce@meta.data$sample[sce@meta.data$orig.ident == "Y00006"] <- "P02"
sce@meta.data$sample[sce@meta.data$orig.ident == "T2"] <- "P03"
sce@meta.data$sample[sce@meta.data$orig.ident == "T3"] <- "P04"
sce@meta.data$sample[sce@meta.data$orig.ident == "T4"] <- "P05"
sce@meta.data$sample[sce@meta.data$orig.ident == "T5"] <- "P06"
sce@meta.data$sample[sce@meta.data$orig.ident == "T6"] <- "P07"
sce@meta.data$sample[sce@meta.data$orig.ident == "T8"] <- "P08"
sce@meta.data$sample[sce@meta.data$orig.ident == "T9"] <- "P09"
sce@meta.data$sample[sce@meta.data$orig.ident == "T10"] <- "P10"
sce@meta.data$sample[sce@meta.data$orig.ident == "Y00008"] <- "MET01"
sce@meta.data$sample[sce@meta.data$orig.ident == "Y00013"] <- "MET02"
sce@meta.data$sample[sce@meta.data$orig.ident == "Y00014"] <- "MET03"
sce@meta.data$sample[sce@meta.data$orig.ident == "Y00016"] <- "MET04"
sce@meta.data$sample[sce@meta.data$orig.ident == "Y00019"] <- "MET05"
sce@meta.data$sample[sce@meta.data$orig.ident == "Y00027"] <- "MET06"

# Add group information
sce@meta.data$group <- ifelse( grepl("MET",sce@meta.data$sample ) ,"MET" ,"PT" )
# Global
p1 <- DimPlot(
  sce, reduction = "umap", pt.size=0.5, label = F,repel = TRUE
)
# Sample
p2 <- DimPlot(
  sce, reduction = "umap",group.by = "sample", pt.size=0.5, label = F,
  repel = TRUE
)
# group
p3 <- DimPlot(
  sce, reduction = "umap",group.by = "group", pt.size=0.5, label = F,
  repel = TRUE
)

p1 + p2 +p3

Harmony to remove batch effect

sce2 <- sce  |>  RunHarmony("sample", plot_convergence = TRUE)
## Transposing data matrix
## Initializing state using k-means centroids initialization
## Harmony 1/10
## Harmony 2/10
## Harmony 3/10
## Harmony 4/10
## Harmony 5/10
## Harmony 6/10
## Harmony 7/10
## Harmony 8/10
## Harmony 9/10
## Harmony 10/10
## Harmony converged after 10 iterations

sce2
## An object of class Seurat 
## 51911 features across 11914 samples within 1 assay 
## Active assay: RNA (51911 features, 2000 variable features)
##  33 layers present: counts.1, counts.2, counts.3, counts.4, counts.5, counts.6, counts.7, counts.8, counts.9, counts.10, counts.11, counts.12, counts.13, counts.14, counts.15, counts.16, data.1, data.2, data.3, data.4, data.5, data.6, data.7, data.8, data.9, data.10, data.11, data.12, data.13, data.14, data.15, data.16, scale.data
##  3 dimensional reductions calculated: pca, umap, harmony
# Same workflow
sce2 <- sce2  |>  
  RunUMAP(reduction = "harmony", dims = 1:30)  |>  
  FindNeighbors(reduction = "harmony", dims = 1:30)  |>  
  FindClusters(resolution = 0.5)  |>  
  identity()
## 15:19:42 UMAP embedding parameters a = 0.9922 b = 1.112
## 15:19:42 Read 11914 rows and found 30 numeric columns
## 15:19:42 Using Annoy for neighbor search, n_neighbors = 30
## 15:19:42 Building Annoy index with metric = cosine, n_trees = 50
## 0%   10   20   30   40   50   60   70   80   90   100%
## [----|----|----|----|----|----|----|----|----|----|
## **************************************************|
## 15:19:42 Writing NN index file to temp file /var/folders/2c/9q3pg2295195bp3gnrgbzrg40000gn/T//RtmpjzstFL/file2afb17134535
## 15:19:42 Searching Annoy index using 1 thread, search_k = 3000
## 15:19:44 Annoy recall = 100%
## 15:19:45 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 30
## 15:19:45 Initializing from normalized Laplacian + noise (using RSpectra)
## 15:19:46 Commencing optimization for 200 epochs, with 516614 positive edges
## 15:19:50 Optimization finished
## Computing nearest neighbor graph
## Computing SNN
## Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
## 
## Number of nodes: 11914
## Number of edges: 465972
## 
## Running Louvain algorithm...
## Maximum modularity in 10 random starts: 0.9043
## Number of communities: 14
## Elapsed time: 1 seconds

p11 <- DimPlot(sce2, reduction = "umap", pt.size=0.5, label = F,repel = TRUE)
p22 <- DimPlot(sce2, reduction = "umap",group.by = "sample", pt.size=0.5, label = F,repel = TRUE)
p33 <- DimPlot(sce2, reduction = "umap",group.by = "group", pt.size=0.5, label = F,repel = TRUE)
p11 + p22 +p33

Visualize marker genes

Marker  <-  list(
  Epi = c("EPCAM"),
  Endo = c("PECAM1","PLVAP"),
  Fibroblast = c("COL3A1","COL1A1","COL1A2"),
  IM = c("PTPRC"),
  B = c("CD79A","CD79B","CD19"),
  T = c("CD3D","CD3E","CD8A","CD4"),
  Myeloid = c("C1QA","C1QB","CD163","CD1C")
)

Marker2 = c(
    "EPCAM",
    "PECAM1","PLVAP",
    "COL3A1","COL1A1","COL1A2",
    'PTPRC',
    "CD79A","CD79B","CD19",
    "CD3D","CD3E","CD8A","CD4",
    "C1QA","C1QB","CD163","CD1C"
)
DotPlot(sce2, features = Marker2, group.by = "RNA_snn_res.0.5")

VlnPlot(sce2, features = Marker2, pt.size = 0, ncol = 5)

FeaturePlot(
    sce2,
    features = c(
        "EPCAM", "PECAM1", "COL3A1", 'PTPRC',
        "CD79A", "CD79B", "CD3D", "CD3E", "C1QA", "C1QB"
    )
)

Annotate clusters

Using Vector

new.cluster.ids <- c(
  'Epi','Epi','Myeloid','Fibroblast','Epi','Epi','Fibroblast','Epi','T','Epi','Fibroblast','Epi','Endo','un','Epi','Epi','Fibroblast','un','Fibroblast'
)
names(new.cluster.ids) <- levels(sce2)

sce2 <- RenameIdents(sce2, new.cluster.ids)

# Add to metadata, for 
sce2@meta.data$new.cluster.ids <- Idents(sce2)

DimPlot(sce2, reduction = "umap", label = TRUE, pt.size = 0.5) +      NoLegend()

Directly assign

Idents(sce2) <- "seurat_clusters"
sce2 <- RenameIdents(sce2,
    "0" = "Epi",
    "1" = "Epi",
    "2" = "Myeloid",
    "3" = "Fibroblast",
    "4" = "Epi",
    "5" = "Epi",
    "6" = "Fibroblast",
    "7" = "Epi",
    "8" = "T",
    "9" = "Epi",
    "10" = "Fibroblast",
    "11" = "Epi",
    "12" = "Endo",
    "13" = "un",
    "14" = "Epi",
    "15" = "Epi",
    "16" = "Fibroblast",
    "17" = "un",
    "18" = "Fibroblast"
)
sce2@meta.data$celltype <- Idents(sce2)
DimPlot(sce2, reduction = "umap",label = TRUE, pt.size = 0.5) + 
  NoLegend()

Add annotation to meta data

sce2$Anno <- "NA"
celltype <- c(
    'Epi', 'Epi', 'Myeloid', 'Fibroblast', 'Epi', 'Epi', 'Fibroblast', 'Epi', 'T', 'Epi', 'Fibroblast', 'Epi', 'Endo', 'un', 'Epi', 'Epi', 'Fibroblast', 'un', 'Fibroblast'
)

# Note:cluster start from 0
# For loop to add
sub_length <- length(unique(sce2$seurat_clusters)) - 1
for (i in 0:sub_length) {
    sce2$Anno[sce2$seurat_clusters == i] = celltype[i + 1]
}

# UMAP
DimPlot(
    sce2, reduction = "umap", group.by = 'Anno', label = TRUE, pt.size = 0.5) +
    NoLegend()



DimPlot(
  sce2, reduction = "umap",label = TRUE, pt.size = 0.5
) + 
  NoLegend()


head(sce2@meta.data)
##                         orig.ident nCount_RNA nFeature_RNA percent.mt
## K16733_AAACATACTCGTTT-1     K16733       2464          965 12.6623377
## K16733_AAAGCAGAACGTTG-1     K16733       7145         1919  2.4492652
## K16733_AAAGCAGACTGAGT-1     K16733       1655          621  2.1752266
## K16733_AAAGGCCTGCTCCT-1     K16733      14272         2771  1.5414798
## K16733_AAATACTGTGGATC-1     K16733      13832         2541  0.3181029
## K16733_AAATTCGAGACGAG-1     K16733       4207         1183  0.1188495
##                         percent.rp percent.HB RNA_snn_res.0.5 seurat_clusters
## K16733_AAACATACTCGTTT-1   13.35227          0              11              11
## K16733_AAAGCAGAACGTTG-1   36.72498          0               5               5
## K16733_AAAGCAGACTGAGT-1   35.52870          0               2               2
## K16733_AAAGGCCTGCTCCT-1   38.81026          0               2               2
## K16733_AAATACTGTGGATC-1   40.73164          0               2               2
## K16733_AAATTCGAGACGAG-1   42.33420          0               6               6
##                         sample group new.cluster.ids   celltype       Anno
## K16733_AAACATACTCGTTT-1    P01    PT             Epi        Epi        Epi
## K16733_AAAGCAGAACGTTG-1    P01    PT             Epi        Epi        Epi
## K16733_AAAGCAGACTGAGT-1    P01    PT         Myeloid    Myeloid    Myeloid
## K16733_AAAGGCCTGCTCCT-1    P01    PT         Myeloid    Myeloid    Myeloid
## K16733_AAATACTGTGGATC-1    P01    PT         Myeloid    Myeloid    Myeloid
## K16733_AAATTCGAGACGAG-1    P01    PT      Fibroblast Fibroblast Fibroblast

# save(sce2, here("learn", "2023_scRNA", "sce.anno.RData"))