主成份分析 (Principal Component Analysis, PCA) 是一种多元分析技术1),其它常用的多元分析技术还有回归分析、聚类分析、因子分析等等。主成份分析由Karl Pearson于1901年发明2),其核心思想在于,在尽可能保留数据的差异的前题下,降低数据的维度,也就是抽象出更少的互不相关的变量来描述各数据。可以通过此在线的演示来理解。
比如,一些基因在不同的样本中有个表达量,对于每个基因来说,它的表达情况就是由这些样本中的表达量来定义的。 如果在坐标系中把所有的基因画出来,每个样本就是一条坐标轴,这这个表达矩阵数据集就是一群在多维空间中的点,在保持这一群点的相对空间位置不变的情况下,旋转到一个新的坐标系(坐标轴就是各个PC),使得各点在一条新的坐标轴上的坐标(投影)的方差最大,得到PC1,然后在旋转,得到次大,得到PC2,依次类推就得到了一个新的坐标系。
主成份分析可通过求取协方差矩阵的本征向量实现,本征值最大的本征向量即为PC13), 比如R的princomp函数采样的就是这种方法;也可通过奇异值分解(singular value decomposition,SVD)来实现,比如R的prcomp函数。
这里采用一组基因表达数据,共九个样本,第一列是基因ID,后边9列是基因表达值,共分为三个组。
dat <- read.delim("ftp://ftp.biolab.wang/data/rpkm.tsv", header=TRUE, sep='\t') # 读入数据
看看这个数据前边几行,可以看到,第一类是geneID,后边九列是九个样本的表达数据。
nrow(dat) ### [1] 22195 head(dat) ## geneID C1 C2 C3 Tx1 Tx2 Tx3 Ty1 Ty2 Ty3 ## 1 ENSMUSG00000006169 16.08108065 13.6536716 14.69375959 15.2574267 12.868341 16.1915416 13.92268906 15.0437726 15.0177982 ## 2 ENSMUSG00000025190 263.13937306 244.4119889 251.45846978 236.3022661 234.562218 231.2300947 234.18234088 253.3412910 238.5972540 ## 3 ENSMUSG00000080725 0.00000000 0.0000000 0.00000000 0.0000000 0.000000 0.0000000 0.00000000 0.0000000 0.0000000 ## 4 ENSMUSG00000017499 0.18095450 0.1254891 0.11109292 0.1550804 0.226933 0.1862202 0.14506731 0.2792597 0.2719463 ## 5 ENSMUSG00000091383 0.00000000 0.0000000 0.00000000 1.8190357 3.802629 3.3804546 0.00000000 0.0000000 0.0000000 ## 6 ENSMUSG00000031710 0.04357382 0.0000000 0.04280186 0.0000000 0.000000 0.0000000 0.05589151 0.0000000 0.0000000 # 把一些低表达的去掉,比如我们需要RPKM平均大于0.1的保留,这个是比较武断的, dat <- dat[rowSums(dat[ , -1]) > 0.9, ] nrow(dat) ## [1] 15644
我们这里使用R的prcomp函数,这个函数把每行当做一个因变量,而每列代表一个自变量,也就是每列是一个维度,有N列,那么就是N维的数据,每个因变量的特征由这么N个维度组成。这里我们要看的是样本的情况,也就是说吧样本当做因变量,将基因看成自变量,总的有15644个基因,那么每个样本有15644维,我们很难看出来哪些样本更像,因此希望通过组成分分析,在新的坐标系下来找到一个角度更好的看出样本之间的差异。要把样本作为因变量,那么就要每个样本一行,所以我们在进行PCA之前先要将dat表达矩阵进行转置。
Dat <- t(dat[ , -1]) pca <- prcomp(Dat) names(pca) ## [1] "sdev" "rotation" "center" "scale" "x"
得到的结果为list,含有5个元素:pca$sdev 为各主成份的标准差,其平方为方差,即本征值,可用于碎石图及计算解释方差的百分比;pca$rotation是本征向量(eigenvectors)矩阵;pca$center 为对数据进行centering所减去的值,也就是列的均值; pca$scale是进行scaling所除以的值,也就是标准差,当scale=F的时候则无这项;pca$x PCs的值,也就是原数据在个PC上的投影。平时的作图用到的是x和sdev。
可以通过一些有用的函数来查看
summary(pca) ## Importance of components: ## PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 ## Standard deviation 4865.6843 1557.80585 864.13727 692.80831 529.65561 441.86436 273.87476 236.647 0.000000000002937 ## Proportion of Variance 0.8475 0.08687 0.02673 0.01718 0.01004 0.00699 0.00269 0.002 0.000000000000000 ## Cumulative Proportion 0.8475 0.93437 0.96110 0.97828 0.98832 0.99531 0.99800 1.000 1.000000000000000
plot(pca) # 等同于 screeplot(pca) #碎石图,自己看看得到的图
PCs的值可以看到如下,这个矩阵的列名是PCs,而行名是样本名,每行一个样本。二维图一般用PC1和PC2来做。
pca$x ## PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 ## C1 -8217.820 -359.0164 1139.3907 -893.29261 45.03219 325.90944 -75.65703 99.53745 0.0000000000059820948 ## C2 2162.592 1120.4435 346.8057 -29.17881 1203.39744 -371.07497 164.04526 20.12227 0.0000000000014653671 ## C3 1151.664 -1697.3988 1189.6159 1215.84519 -125.97493 -162.65799 -191.36758 -109.08803 -0.0000000000096565178 ## Tx1 -1938.648 -996.6127 -1493.2726 307.35304 339.40794 167.41032 -351.50173 254.73485 0.0000000000015678661 ## Tx2 -1813.871 -1080.2482 -540.2138 -514.89785 -489.98033 -919.74632 162.71762 -19.27196 0.0000000000045162513 ## Tx3 -5793.606 2228.2790 -609.4241 651.34727 -205.47040 170.12816 115.51534 -316.55937 -0.0000000000003566324 ## Ty1 6567.873 1772.4781 162.7143 -663.68684 -347.70749 -20.54642 -427.91542 -96.68661 -0.0000000000021449558 ## Ty2 4665.662 -1976.1140 -415.2146 -446.46695 84.04496 578.11576 271.99849 -260.63401 0.0000000000004959860 ## Ty3 3216.155 988.1894 219.5985 372.97756 -502.74937 232.46202 332.16505 427.84540 0.0000000000024031955
可以通过ggplot2作比较漂亮的图
# 先载入需要用的包,ggplot2用来作图 library(ggplot2) # 然后把PC做坐标信息转为一个data.frame PCs <- data.frame(pca$x) # 添加每个样本的组名,这样方便后边作图的时候根据组来映射到颜色 PCs$Group <- c("C", "C", "C", "Tx", "Tx", "Tx", "Ty", "Ty", "Ty") # 作图: ggplot() + geom_point(data=PCs, aes(x=PC1, y=PC2, colour = Group)) + xlab(sprintf("PC1: %.2f%%", (100*(pca$sdev)^2)[1]/sum((pca$sdev)^2))) + ylab(sprintf("PC2: %.2f%%", (100*(pca$sdev)^2)[2]/sum((pca$sdev)^2)))
看起来C和Tx两组间的差异就不是那么明显,不能很好地区分。这可能是基因表达差异巨大,那么可能一些表达很高的基因导致了我们观察的偏差。试试,对表达值进行log转化,看看效果
Dat <- t(dat[ , -1]) Dat <- log10(Dat + 0.01) pca <- prcomp(Dat) PCs <- data.frame(pca$x) PCs$Group <- c("C", "C", "C", "Tx", "Tx", "Tx", "Ty", "Ty", "Ty") ggplot() + geom_point(data=PCs, aes(x=PC1, y=PC2, colour = Group)) + xlab(sprintf("PC1: %.2f%%", (100*(pca$sdev)^2)[1]/sum((pca$sdev)^2))) + ylab(sprintf("PC2: %.2f%%", (100*(pca$sdev)^2)[2]/sum((pca$sdev)^2)))
很显然,进过log转化之后,效果不一样了。
另外,可以对每个基因在每个样本的表达进行一个标准化,这样看的就是基因的表达趋势在样本之间的差异:
Dat <- t(dat[ , -1]) Dat <- scale(Dat) pca <- prcomp(Dat) PCs <- data.frame(pca$x) PCs$Group <- c("C", "C", "C", "Tx", "Tx", "Tx", "Ty", "Ty", "Ty") ggplot() + geom_point(data=PCs, aes(x=PC1, y=PC2, colour = Group)) + xlab(sprintf("PC1: %.2f%%", (100*(pca$sdev)^2)[1]/sum((pca$sdev)^2))) + ylab(sprintf("PC2: %.2f%%", (100*(pca$sdev)^2)[2]/sum((pca$sdev)^2)))
描述基因之间的差异的时候,把基因看做因变量,样本看做自变量,原来的表达矩阵就是每行一个基因,做主成份分析的时候不需要再做转置了。
# 加上行名,这样最后的结果是会带上基因名称的 row.names(dat) <- dat$geneID pca <- prcomp(dat[ , -1]) PCs <- data.frame(pca$x) ggplot() + geom_point(data=PCs, aes(x=PC1, y=PC2)) + xlab(sprintf("PC1: %.2f%%", (100*(pca$sdev)^2)[1]/sum((pca$sdev)^2))) + ylab(sprintf("PC2: %.2f%%", (100*(pca$sdev)^2)[2]/sum((pca$sdev)^2)))
对每个基因在每个样本的表达进行一个标准化,然后看看效果
Dat <- t(scale(t(dat[ , -1]))) pca <- prcomp(Dat) PCs <- data.frame(pca$x) ggplot() + geom_point(data=PCs, aes(x=PC1, y=PC2)) + xlab(sprintf("PC1: %.2f%%", (100*(pca$sdev)^2)[1]/sum((pca$sdev)^2))) + ylab(sprintf("PC2: %.2f%%", (100*(pca$sdev)^2)[2]/sum((pca$sdev)^2)))
我们这里做一个示例的数据集,更好地看出展示基因之间的差异效果,这里虚拟一个有20个样本,250个基因的表达矩阵。
Dat <- matrix(data = c(rnorm(10^3, mean = 1, sd = 1), # 一千个数字,对应50行 rnorm(10^3, mean = 3, sd = 1), rnorm(10^3, mean = 5, sd = 1), rnorm(10^3, mean = 6, sd = 2), rnorm(10^3, mean = 9, sd = 4)), # 方差越大,对应后边点越散 nrow = 250, ncol = 20, byrow = TRUE) # 每50行对应一簇,名称中给带同样的字母,方便后边提取簇的名称 rownames(Dat) <- sprintf("%s%d", rep(letters[1:5], each=50), rep(1:50, times=5)) # 样本名称 colnames(Dat) <- LETTERS[1:20]
pca <- prcomp(Dat) PCs <- data.frame(pca$x) PCs$cluster <- substr(rownames(pca$x),1,1) ggplot() + geom_point(data=PCs, aes(x=PC1, y=PC2, colour=cluster)) + xlab(sprintf("PC1: %.2f%%", (100*(pca$sdev)^2)[1]/sum((pca$sdev)^2))) + ylab(sprintf("PC2: %.2f%%", (100*(pca$sdev)^2)[2]/sum((pca$sdev)^2))) + theme_bw() + theme(legend.position="none")
我们能够很清楚看到,表达量相近的基因会聚到一起。而方差越大,点之间越散,相互之间的相似性就越低。 这里我们差不多只需要PC1就能够把这些基因分成五个簇。
很显然,如果按照20个样本作为坐标系来描述这些点,那么我们很难通过任意两个坐标轴来把这些基因之间的差异展示出来。比如我们看看A和B两个样本展示情况:
DAT <- as.data.frame(Dat) DAT$cluster <- substr(rownames(DAT),1,1) ggplot() + geom_point(data=DAT, aes(x=A, y=B, colour=cluster)) + theme_bw() + theme(legend.position="none")
显然不容易把这些基因区分成五个簇。
本网页已被访问9156次。