单细胞36计之30- 基于FPKM标准化表达矩阵做单细胞差异分析

30、第三十计 反客为主
本是客人却用主人的口气说话。后指在一定的场合下采取主动措施,以声势压倒别人。

代码框较乱,点这里阅读原文
最近看到有一个简单的数据挖掘文章,就做了一个单细胞表达矩阵的差异分析,然后强行解释一波。而且使用的是基于FPKM标准化表达矩阵载入seurat包进行分析,就随便使用我们学习班获得的技能复现一下,分享给广大读者。

  • 1、概述

  • 计算基因长度

  • 1.1 单细胞差异分析pipeline

  • 1.2 count标准化

  • 2、官方fpkm数据差异分析

  • 2.1 表达矩阵与分组信息

  • 2.2 ID转换

  • 2.3 创建seurat,质控,差异分析一键操作

  • 2.4 差异结果可视化

  • 3、根据count矩阵转换fpkm并完成差异分析

  • 3.1 导入count矩阵

  • 3.2 计算fpkm矩阵

  • 3.3 差异分析

  • 3.4 可视化

前言:使用GSE81861提供的数据,比较CRC肿瘤上皮细胞与正常上皮细胞的差异。

GEO提供了count与fpkm两种数据。笔记内容先用官方的fpkm数据做差异分析,再利用counts数据手动计算fpkm矩阵,完成差异分析。最后比较两种方法的结果是否存在差异。

https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE81861

注:因为有不少重复的步骤,故设置较多的函数。

1、概述

1.1 单细胞差异分析pipeline

简单来说分为三步:首先导入、制备规范的表达矩阵以及分组信息;然后利用Seurat包构建seurat对象,归一化;最后进行差异分析,以及结果的可视化。

1.2 count标准化

主要受测序文库(样本总read数)与基因长度的影响,测序的counts数据不能直接进行差异分析,需要进行标准化处理。常见的几种标准化方法简单介绍如下–

  • rpkm:counts先对测序文库标准化,再对基因长度标准化;

  • fpkm:FPKM同RPKM是一样的,只是RPKM用于单末端测序,而FPKM用于双末端测序;

  • tpm:counts先对基因长度标准化,再对测序文库标准化;

  • cpm:counts只对测序文库标准化。

测序文库相对容易计算,直接使用colSums()函数即可;而基因长度则比较难求,首先要了解基因长度有不同的定义标准,其次要知道哪些R包提供相关生物数据。我目前了解到了以下三种方法,以及根据与官方fpkm验证,最终选择第三种方法用于后续的分析。

计算基因长度

(1)TxDb.Hsapiens.UCSC.hg19.knownGene包

if (!require("TxDb.Hsapiens.UCSC.hg19.knownGene", quietly = TRUE))  BiocManager::install("TxDb.Hsapiens.UCSC.hg19.knownGene")txdb <- TxDb.Hsapiens.UCSC.hg19.knownGeneexon_txdb=exons(txdb)genes_txdb=genes(txdb)
g_l.1–根据非冗余外显子之和定义
g_l_1 <- function(){  o <- findOverlaps(exon_txdb,genes_txdb)  t1=exon_txdb[queryHits(o)]  t2=genes_txdb[subjectHits(o)]  t1=as.data.frame(t1)  t1$geneid=mcols(t2)[,1]  # 得到exon_id与geneid的对应关系  g_l.1 <- lapply(split(t1,t1$geneid),function(x){    #按gene id拆分表格    head(x)    tmp=apply(x,1,function(y){      y[2]:y[3]    }) #根据每一个gene所有exon的区间,生成区间内的整数,返回的为list。    length(unique(unlist(tmp)))    #计算共有多少种整数,即为最终的非冗余exon长度之和  })  head(g_l.1) #为一个list  g_l.1=data.frame(gene_id=names(g_l.1),length=as.numeric(g_l.1))  dim(g_l.1)  head(g_l.1)  #为基因ID增添ENSEMBLE ID  library(org.Hs.eg.db)  s2g=toTable(org.Hs.egENSEMBL)  head(s2g)  g_l.1=merge(g_l.1,s2g,by='gene_id')  #把g_l,s2g两个数据框以'gene_id'为连接进行拼接  head(g_l.1)  return(g_l.1)}g_l.1 <- g_l_1()head(g_l.1)
##   gene_id length      ensembl_id## 1       1   7255 ENSG00000121410## 2      10   1317 ENSG00000156006## 3     100   1532 ENSG00000196839## 4    1000   4570 ENSG00000170558## 5   10000   7458 ENSG00000117020## 6   10000   7458 ENSG00000275199
g_l.2—-根据最长转录本定义
g_l_2 <- function(){  t_l=transcriptLengths(txdb)  head(t_l)  t_l=na.omit(t_l)  #先按基因ID,再按转录本长度从大到小排序  t_l=t_l[order(t_l$gene_id,t_l$tx_len,decreasing = T),]  head(t_l);dim(t_l)  #根据gene_id去重,选择第一个,也就是最长的那个  t_l=t_l[!duplicated(t_l$gene_id),]  head(t_l);dim(t_l)  g_l.2=t_l[,c(3,5)]  library(org.Hs.eg.db)  s2g=toTable(org.Hs.egENSEMBL)  g_l.2=merge(g_l.2,s2g,by='gene_id')  head(g_l.2)  return(g_l.2)}g_l.2 <- g_l_2()head(g_l.2)
##   gene_id tx_len      ensembl_id## 1       1   3419 ENSG00000121410## 2      10   1317 ENSG00000156006## 3     100   1532 ENSG00000196839## 4    1000   4367 ENSG00000170558## 5   10000   7082 ENSG00000275199## 6   10000   7082 ENSG00000117020

(2)biomaRt包

g_l.3–根据最长转录本定义
g_l_3 <- function(){  if (!require("biomaRt", quietly = TRUE))    BiocManager::install("biomaRt")  ensembl <- useMart("ensembl") #connect to a specified BioMart database  ensembl = useDataset("hsapiens_gene_ensembl",mart=ensembl)  #use the hsapiens(人类) dataset.或者直接如下设置  #ensembl = useMart("ensembl",dataset="hsapiens_gene_ensembl")  test <- getBM(attributes=c('ensembl_gene_id', 'start_position',                             'end_position','ensembl_transcript_id',                             'transcript_length'),                mart = ensembl)  test <- test[order(test$ensembl_gene_id,test$transcript_length,                     decreasing = T),]  g_l.3 <- test[!duplicated(test$ensembl_gene_id),]  g_l.3 <- g_l.3[,c(1,5)]  head(g_l.3)  return(g_l.3)}g_l.3 <- g_l_3()

比较三种结果的差异

dim(g_l.1);dim(g_l.2);dim(g_l.3)
## [1] 23729     3
## [1] 25159     3
## [1] 67130     2
g_l.1 <- g_l.1[,-1]colnames(g_l.1) <- c("g_l.1","ensembl_id")g_l.2 <- g_l.2[,-1]colnames(g_l.2) <- c("g_l.2","ensembl_id")colnames(g_l.3) <- c("ensembl_id","g_l.3")g_l_all <- merge(g_l.1, g_l.2, by="ensembl_id")g_l_all <- merge(g_l_all, g_l.3, by="ensembl_id")head(g_l_all,10)
##         ensembl_id g_l.1 g_l.2 g_l.3## 1  ENSG00000000003  2486  2069  3796## 2  ENSG00000000005  3031  3031  1205## 3  ENSG00000000419  1069  1069  1161## 4  ENSG00000000457  3426  3090  6308## 5  ENSG00000000460  5654  3852  4355## 6  ENSG00000000938  3470  2485  2637## 7  ENSG00000000971  4386  4127  6985## 8  ENSG00000001036  4415  2749  2385## 9  ENSG00000001084  6221  3812  3785## 10 ENSG00000001167  6589  6385  3811
summary(g_l_all)
##   ensembl_id            g_l.1            g_l.2            g_l.3##  Length:23906       Min.   :    20   Min.   :    20   Min.   :    27##  Class :character   1st Qu.:  1636   1st Qu.:  1472   1st Qu.:  1660##  Mode  :character   Median :  2964   Median :  2515   Median :  2902##                     Mean   :  3760   Mean   :  3083   Mean   :  3564##                     3rd Qu.:  4934   3rd Qu.:  4104   3rd Qu.:  4743##                     Max.   :117846   Max.   :109223   Max.   :109224
#最终选择第三种结果g_l <- g_l.3

2、官方fpkm数据差异分析

2.1 表达矩阵与分组信息

#表达矩阵nm_FPKM <- read.csv("GSE81861_CRC_NM_epithelial_cells_FPKM.csv/GSE81861_CRC_NM_epithelial_cells_FPKM.csv")data_input <- function(data){  #data <- nm_FPKM  row.names(data) <- data[,1]  data <- data[,-1]  data <- as.matrix(data)  rownames(data) <- sapply(strsplit(rownames(data),"_"),"[",3)  rownames(data) <- sapply(strsplit(rownames(data),"[.]"),"[",1)  colnames(data) <- sapply(strsplit(colnames(data),"_"),"[",1)  data <- data[grep("ENSG",rownames(data)),]  return(data)}nm_FPKM <- data_input(nm_FPKM)dim(nm_FPKM)  #56380个基因 #160个样本
## [1] 56380   160
nm_FPKM[1:4,1:4]
##                 RHC4104 RHC6087 RHL2880   RHC5949## ENSG00000000003   0.000 185.315 323.203 22.311700## ENSG00000000005   0.000   0.000   0.000  0.000000## ENSG00000000419   0.000 231.756   0.000  0.851514## ENSG00000000457 100.135   0.000   0.000  0.000000
tumor_FPKM <- read.csv("GSE81861_CRC_tumor_epithelial_cells_FPKM.csv/GSE81861_CRC_tumor_epithelial_cells_FPKM.csv")tumor_FPKM <- data_input(tumor_FPKM)tumor_FPKM[1:4,1:4]
##                 RHC4075 RHC5563 RHC5552 RHC4874## ENSG00000000003       0   0.000       0       0## ENSG00000000005       0   0.000       0       0## ENSG00000000419       0   0.000       0       0## ENSG00000000457       0 193.167       0       0
dim(tumor_FPKM)#56380个基因 #272个样本
## [1] 56380   272
exp_FPKM <- cbind(nm_FPKM,tumor_FPKM)dim(exp_FPKM) # 56380个基因   432个样本
## [1] 56380   432
#分组信息group_dat <- data.frame(group=c(rep('normal',160),                                rep('tumor',272)),                        row.names = colnames(exp_FPKM))

2.2 ID转换

因为seurat质控需要过滤线粒体基因,所以需要把ensembl ID转换为 symbol ID

exp_FPKM[1:4,1:4]
##                 RHC4104 RHC6087 RHL2880   RHC5949## ENSG00000000003   0.000 185.315 323.203 22.311700## ENSG00000000005   0.000   0.000   0.000  0.000000## ENSG00000000419   0.000 231.756   0.000  0.851514## ENSG00000000457 100.135   0.000   0.000  0.000000
id_change <- function(data){  print(dim(data))  library(org.Hs.eg.db)  ids1 <- data.frame(ID=c(1:nrow(data)),                     ensembl_id=rownames(data))  ids2 <- merge(toTable(org.Hs.egENSEMBL),                toTable(org.Hs.egSYMBOL),by="gene_id")  ids <- merge(ids1, ids2, by="ensembl_id")  data <- data[ids$ID,]  rownames(data) <- ids$symbol  print(dim(data))  return(data)}exp_FPKM <- id_change(exp_FPKM) #有2w+个ensembl ID没配对到 symbol
## [1] 56380   432## [1] 24941   432
exp_FPKM[1:4,1:4]
##        RHC4104 RHC6087 RHL2880   RHC5949## TSPAN6   0.000 185.315 323.203 22.311700## TNMD     0.000   0.000   0.000  0.000000## DPM1     0.000 231.756   0.000  0.851514## SCYL3  100.135   0.000   0.000  0.000000

2.3 创建seurat,质控,差异分析一键操作

scRNA_deg <- function(exp,group){  library(Seurat)  print("创建seurat对象...")  scRNA <- CreateSeuratObject(counts=exp,                              meta.data=group)  print("质控...")  scRNA[["percent.mt"]] <- PercentageFeatureSet(scRNA, pattern = "^MT-")  minGene=500;maxGene=4000;pctMT=15  scRNA <- subset(scRNA, subset = nFeature_RNA > minGene & nFeature_RNA < maxGene & percent.mt < pctMT)  print("归一化...")  scRNA <- NormalizeData(scRNA, normalization.method = "LogNormalize", scale.factor = 10000)  print("差异分析...")  diff_dat <- FindMarkers(scRNA,ident.1="normal",ident.2="tumor",                          group.by='group')  }FPKM_diff <- scRNA_deg(exp=exp_FPKM, group=group_dat)
## [1] "创建seurat对象..."## [1] "质控..."## [1] "归一化..."## [1] "差异分析..."
head(FPKM_diff)
##               p_val avg_logFC pct.1 pct.2    p_val_adj## GUCA2A 1.353485e-30  2.576937 0.742 0.101 3.375728e-26## CLCA4  3.106166e-30  1.112995 0.688 0.067 7.747089e-26## MT1E   7.460402e-28  1.733571 0.789 0.162 1.860699e-23## CKB    1.723524e-27  2.241367 0.883 0.285 4.298641e-23## ZG16   1.936670e-27  2.753875 0.664 0.073 4.830248e-23## PHGR1  5.482718e-27  1.882931 0.938 0.531 1.367445e-22
dim(FPKM_diff)
## [1] 1421    5
FPKM_diff <- FPKM_diff[FPKM_diff$p_val<0.01 & abs(FPKM_diff$avg_logFC)>0.8,]dim(FPKM_diff)
## [1] 122   5
exp_FPKM_diff <- exp_FPKM[match(rownames(FPKM_diff),rownames(exp_FPKM)),]

2.4 差异结果可视化

热图
my_heatmap <- function(exp_dat){  n <- t(scale(t(exp_dat)))  n[n>2]=2;n[n< -2]= -2  library(pheatmap)  pheatmap(n, show_rownames = F,           show_colnames = F,           annotation_col = group_dat)}p.exp_FPKM_diff <- my_heatmap(exp_FPKM_diff)p.exp_FPKM_diff
图片
箱图
my_boxplot <- function(data,i){  library(ggpubr)  if(!is.numeric(i)){    i=match(i,rownames(data))  }  df <- data.frame(gene=data[i,], group=group_dat$group)  ggboxplot(df,x="group",y="gene",            color = "group",add = "jitter",            ylab = rownames(data)[i]) +    theme_bw()}my_boxplot(exp_FPKM_diff, 2)
图片
my_boxplot(exp_FPKM_diff, "PGK1")
图片

3、根据count矩阵转换fpkm并完成差异分析

3.1 导入count矩阵

nm_COUNT <- read.csv("GSE81861_CRC_NM_epithelial_cells_COUNT.csv/GSE81861_CRC_NM_epithelial_cells_COUNT.csv")nm_COUNT <- data_input(nm_COUNT)nm_COUNT[1:4,1:4]
##                 RHC4104 RHC6087 RHL2880 RHC5949## ENSG00000000003       0     428      66     141## ENSG00000000005       0       0       0       0## ENSG00000000419       0     179       0       1## ENSG00000000457     465       0       0       0
dim(nm_COUNT)
## [1] 56380   160
tumor_COUNT <- read.csv("GSE81861_CRC_tumor_epithelial_cells_COUNT.csv/GSE81861_CRC_tumor_epithelial_cells_COUNT.csv")tumor_COUNT <- data_input(tumor_COUNT)tumor_COUNT[1:4,1:4]
##                 RHC4075 RHC5563 RHC5552 RHC4874## ENSG00000000003       0       0       0       0## ENSG00000000005       0       0       0       0## ENSG00000000419       0       0       0       0## ENSG00000000457       0     133       0       0
dim(tumor_COUNT)
## [1] 56380   272

3.2 计算fpkm矩阵

my_FPKM <- function(counts,g_l){  ####根据有基因长度的基因,筛选矩阵子集  #counts=nm_COUNT  ng=intersect(rownames(counts),g_l$ensembl_id)  length(ng)  lengths=g_l[match(ng,g_l$ensembl_id),2]  names(lengths) <- g_l[match(ng,g_l$ensembl_id),1]  head(lengths)  #### 计算样本文库大小,以及最后的fpkm计算  counts <- counts[names(lengths),]  counts[1:4,1:4]  total_count <- colSums(counts)  head(total_count)  #根据counts、length、total_count计算fpkm  FPKM <- t(do.call( rbind,                     lapply(1:length(total_count),                            function(i){                              10^9*counts[,i]/lengths/total_count[i]                              #lengths向量自动遍历                            }) ))  FPKM[1:4,1:4]  return(FPKM)}nm_my_FPKM <- my_FPKM(counts = nm_COUNT,                      g_l = g_l)colnames(nm_my_FPKM) <- colnames(nm_COUNT)nm_my_FPKM[1:4,1:4]
##                  RHC4104  RHC6087  RHL2880    RHC5949## ENSG00000000003   0.0000 160.5715 168.6839 22.7021034## ENSG00000000005   0.0000   0.0000   0.0000  0.0000000## ENSG00000000419   0.0000 219.5694   0.0000  0.5264304## ENSG00000000457 124.2016   0.0000   0.0000  0.0000000
dim(nm_my_FPKM)
## [1] 50813   160
tumor_my_FPKM <- my_FPKM(counts = tumor_COUNT,                      g_l = g_l)dim(tumor_FPKM)
## [1] 56380   272
colnames(tumor_my_FPKM) <- colnames(tumor_COUNT)nm_my_FPKM[1:4,1:4]
##                  RHC4104  RHC6087  RHL2880    RHC5949## ENSG00000000003   0.0000 160.5715 168.6839 22.7021034## ENSG00000000005   0.0000   0.0000   0.0000  0.0000000## ENSG00000000419   0.0000 219.5694   0.0000  0.5264304## ENSG00000000457 124.2016   0.0000   0.0000  0.0000000

3.3 差异分析

exp_my_FPKM <- cbind(nm_my_FPKM,tumor_my_FPKM)dim(exp_my_FPKM) #转换id前
## [1] 50813   432
exp_my_FPKM[1:4,1:4] #转换id前
##                  RHC4104  RHC6087  RHL2880    RHC5949## ENSG00000000003   0.0000 160.5715 168.6839 22.7021034## ENSG00000000005   0.0000   0.0000   0.0000  0.0000000## ENSG00000000419   0.0000 219.5694   0.0000  0.5264304## ENSG00000000457 124.2016   0.0000   0.0000  0.0000000
exp_my_FPKM <- id_change(exp_my_FPKM)
## [1] 50813   432## [1] 24931   432
dim(exp_my_FPKM) #转换id后
## [1] 24931   432
exp_my_FPKM[1:4,1:4] #转换id后
##         RHC4104  RHC6087  RHL2880    RHC5949## TSPAN6   0.0000 160.5715 168.6839 22.7021034## TNMD     0.0000   0.0000   0.0000  0.0000000## DPM1     0.0000 219.5694   0.0000  0.5264304## SCYL3  124.2016   0.0000   0.0000  0.0000000
my_FPKM_diff <- scRNA_deg(exp=exp_my_FPKM, group=group_dat)
## [1] "创建seurat对象..."## [1] "质控..."## [1] "归一化..."## [1] "差异分析..."
head(my_FPKM_diff)
##               p_val avg_logFC pct.1 pct.2    p_val_adj## CLCA4  2.640706e-30 1.6080767 0.688 0.067 6.583544e-26## GUCA2A 3.289287e-30 2.4478445 0.742 0.101 8.200520e-26## ZG16   1.629645e-27 2.9466466 0.664 0.073 4.062867e-23## MT1E   2.707239e-27 1.4704522 0.789 0.162 6.749417e-23## CKB    5.633316e-27 1.9409546 0.883 0.285 1.404442e-22## PADI2  2.153941e-26 0.5522112 0.734 0.128 5.369989e-22
dim(my_FPKM_diff)
## [1] 1231    5
my_FPKM_diff <- my_FPKM_diff[my_FPKM_diff$p_val<0.01 & abs(my_FPKM_diff$avg_logFC)>0.8,]dim(my_FPKM_diff)
## [1] 112   5
exp_my_FPKM_diff <- exp_my_FPKM[match(rownames(my_FPKM_diff),rownames(exp_my_FPKM)),]

3.4 可视化

热图
my_heatmap(exp_my_FPKM_diff)
图片
箱图
my_boxplot(exp_my_FPKM_diff, 2)
图片
my_boxplot(exp_my_FPKM_diff, "PGK1")
图片
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容