# 3.4 降维

## 1 前言

基本上scRNA数据分析步骤都会包含基于基因表达量来对细胞进行比较。例如：细胞聚类就是将相似表达谱的细胞聚集在一起，而如何判断基因表达谱是否相似，就是计算基因间的欧氏距离。

scRNA分析就是在于数据打交道。本来看不见摸不着的基因，现在体现在数据上，虽然还是摸不着，但能看得到。**每个基因现在都作为数据的一个维度存在**。假如我们有一个scRNA数据集，其中只有两个基因，其中两个坐标轴表示两个基因的表达量，图中的点就表示细胞，每个细胞在图上都由x、y轴确定。也就是说，**基因的表达决定了细胞的分布**

如果有两个基因，那么细胞的位置就由两个维度决定；三个基因，就由 三维空间决定；而我们有2万多个基因，那么就存在2万多个维度（高维空间）。

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-23-012726.png)

高维空间我们无法理解，最常见的当属二维和三维，如果再加上时间线，就是四维空间。

**降维，顾名思义，就是把各种离散的维度“去粗存精”**。因为各个基因并不是毫无关联，同一生物学过程的各个基因是相互关联的，因此它们各自的维度就可以进行整合，结果可以用一个维度表示相互关联的多个维度，例如基因共表达网络中的eigengene

降维的作用非常重大，下游分析中的聚类只利用少量的维度进行计算，会比使用全部的维度计算更快，结果也更容易理解（比如我们目前最多只能做出三维的图片）。

### **数据准备之sce.zeisel**

数据依然给大家准备好了，`sce.zeisel`链接：<https://share.weiyun.com/mNwJS8U9> 密码：g8379h

```r
library(scRNAseq)
# 将每个细胞的所有基因表达量加起来，得到每个细胞的文库大小
library(scater)
sce.zeisel <- aggregateAcrossFeatures(sce.zeisel, 
                                      id=sub("_loc[0-9]+$", "", rownames(sce.zeisel)))

# 基因注释
library(org.Mm.eg.db)
rowData(sce.zeisel)$Ensembl <- mapIds(org.Mm.eg.db, 
    keys=rownames(sce.zeisel), keytype="SYMBOL", column="ENSEMBL")

# 质控（先perCellQCMetrics，后quickPerCellQC）
stats <- perCellQCMetrics(sce.zeisel, subsets=list(
    Mt=rowData(sce.zeisel)$featureType=="mito"))

qc <- quickPerCellQC(stats, percent_subsets=c("altexps_ERCC_percent", 
                                              "subsets_Mt_percent"))
sce.zeisel <- sce.zeisel[,!qc$discard]

# 归一化（去卷积方法）
library(scran)
set.seed(1000)
clusters <- quickCluster(sce.zeisel)
sce.zeisel <- computeSumFactors(sce.zeisel, cluster=clusters) 
sce.zeisel <- logNormCounts(sce.zeisel)

# 利用spike-in找HVGs（前10%）
dec.zeisel <- modelGeneVarWithSpikes(sce.zeisel, "ERCC")
top.hvgs <- getTopHVGs(dec.zeisel, prop=0.1)

sce.zeisel
## class: SingleCellExperiment 
## dim: 19839 2816 
## metadata(0):
## assays(2): counts logcounts
## rownames(19839): 0610005C13Rik 0610007N19Rik ... Zzef1 Zzz3
## rowData names(2): featureType Ensembl
## colnames(2816): 1772071015_C02 1772071017_G12 ... 1772063068_D01
##   1772066098_A12
## colData names(11): tissue group # ... level2class sizeFactor
## reducedDimNames(0):
## altExpNames(2): ERCC repeat
```

### **数据准备之PBMC**

具体的操作在上一章也提到过

```r
# 下载
library(BiocFileCache)
bfc <- BiocFileCache("raw_data", ask = FALSE)
raw.path <- bfcrpath(bfc, file.path("http://cf.10xgenomics.com/samples",
    "cell-exp/2.1.0/pbmc4k/pbmc4k_raw_gene_bc_matrices.tar.gz"))
untar(raw.path, exdir=file.path(tempdir(), "pbmc4k"))

# 读取
suppressMessages(library(DropletUtils))
fname <- file.path(tempdir(), "pbmc4k/raw_gene_bc_matrices/GRCh38")
sce.pbmc <- read10xCounts(fname, col.names=TRUE)
> dim(sce.pbmc)
[1]  33694 737280

# 基因注释之ID整合
library(scater)
rownames(sce.pbmc) <- uniquifyFeatureNames(
    rowData(sce.pbmc)$ID, rowData(sce.pbmc)$Symbol)
# 基因注释之添加位置信息
library(EnsDb.Hsapiens.v86)
location <- mapIds(EnsDb.Hsapiens.v86, keys=rowData(sce.pbmc)$ID, 
    column="SEQNAME", keytype="GENEID")

# 空液滴检测
set.seed(100)
e.out <- emptyDrops(counts(sce.pbmc))
sce.pbmc <- sce.pbmc[,which(e.out$FDR <= 0.001)]

# 质控
stats <- perCellQCMetrics(sce.pbmc, subsets=list(Mito=which(location=="MT")))
high.mito <- isOutlier(stats$subsets_Mito_percent, type="higher")
sce.pbmc <- sce.pbmc[,!high.mito]

# 归一化（去卷积）
library(scran)
set.seed(1000)
clusters <- quickCluster(sce.pbmc)
sce.pbmc <- computeSumFactors(sce.pbmc, cluster=clusters)
sce.pbmc <- logNormCounts(sce.pbmc)

# 利用数据分布来表示技术噪音，并挑选HVGs
set.seed(1001)
dec.pbmc <- modelGeneVarByPoisson(sce.pbmc)
top.pbmc <- getTopHVGs(dec.pbmc, prop=0.1)
```

## 2 主成分分析( PCA)概览

> 它的计算过程之前写过：[StatQuest-PCA学习](https://mp.weixin.qq.com/s/WYIC-JdmgMC_olR-j-fsHw) 以及 [StatQuest--在R中拆解PCA](https://mp.weixin.qq.com/s/kBpwZgmY_A-UcRpGxM1G-A)，其中提到：
>
> * **PCA的作用**就是：对超过4维的数据降维到一个2D/3D平面图中，并且这个图中"**相似相聚"**
> * **为什么可以通过PCA可以看批次效应？**&#x56E0;为PCA图中的每个点都是一个样本，这个点中包含了大量的基因表达量信息；如果说本来属于生物学重复的几个样本在PCA图上离得很远，那么就意味着它们包含的基因表达量差异很大，这是不符合实际的，因此可能存在批次效应

PCA全称Principal components analysis，一般最常见的是二维的图，x轴是PC1，y轴是PC2。PC1捕获了细胞间最大的差异（暂且称它为V1），PC2与PC1正交，捕获了除V1外的最大差异。这样看来，**前几个PCs就能捕获整个数据集主要的差异因素**

我们**降维也是想得到这几个能包含主要生物差异的PCs，方便下游分析**【其实可以看到，一步步走来，都是上一步在为下一步省事省力做贡献，逐渐降低分析难度】。这几个包含主要生物差异的PCs其实也就是前几个PCs，PC越往后越集中在技术误差。

基本每个包都有自己PCA的函数，例如scater可以用`runPCA` ，seurat的`RunPCA`，monocle V3的`reduce_dimension` ，**下面以scater包为例：**

首先还是选择前多少的HVGs，然后给到`runPCA`默认计算前50个PCs，然后结果保存在`SingleCellExperiment`对象的`reducedDims`模块中

```r
library(scran)
top.zeisel <- getTopHVGs(dec.zeisel, n=2000)

library(scater)
set.seed(100) #因为PCA是随机的
sce.zeisel <- runPCA(sce.zeisel, subset_row=top.zeisel) 
reducedDimNames(sce.zeisel)
## [1] "PCA"
dim(reducedDim(sce.zeisel, "PCA")) # 50个PCs，2000多个细胞
## [1] 2816   50
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-29-023058.png)

如果是更大的数据，可以用近似的SVD算法（奇异值分解），scater或scran都可以直接通过函数计算SVD，利用参数`BSPARAM=`传递一个`BiocSingularParam`对象到`runPCA`中

* [SVD与PCA的介绍](https://www.cnblogs.com/bjwu/p/9280492.html)
* SVD是一种矩阵分解方法，相当于因式分解，目的纯粹就是将一个矩阵拆分成多个矩阵相乘的形式
* PCA从名字上就很直观，找到矩阵的主成分，也就意味这从一出生这就是个降维的方法。
* 对于稀疏矩阵来说，SVD更适用，这样对于大数据来说节省了很大空间

```r
library(BiocSingular)
set.seed(1000)
sce.zeisel <- runPCA(sce.zeisel, subset_row=top.zeisel, 
    BSPARAM=RandomParam(), name="IRLBA")
reducedDimNames(sce.zeisel)
## [1] "PCA"   "IRLBA"
> dim(reducedDim(sce.zeisel, "IRLBA"))
[1] 2816   50
```

## 3 选择合适的PCs数

> 看到这个问题，好像又回到了之前选择top HVGs的时候了，那么主成分数应该选多少合适呢？

* 如果选的PC多，可以避免丢弃一些生物信号，但同时又增加了噪音的风险
* 很多有经验的人会设置PC数在一个“合理”区间，例如10-50之间

下面我们来看看，如果经验不足，应该怎么帮助判断？

### **3.1 利用elbow point**

elbow point就是在它之前变化幅度很大，之后变化幅度很小，属于一个转折点。

```r
# 首先提取各个PCs对整体差异的贡献比例
percent.var <- attr(reducedDim(sce.zeisel), "percentVar")
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-29-024354.png)

可以看到前几个主成分的贡献最高，越往后越小

```r
# 辅助选择
chosen.elbow <- PCAtools::findElbowPoint(percent.var)
chosen.elbow
## [1] 7
# 再画个图
plot(percent.var, xlab="PC", ylab="Variance explained (%)")
abline(v=chosen.elbow, col="red")
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-29-024534.png)

上面基于的假设是：每个PCs都能捕获一些生物差异，而且前面的PC比后面的PC包含的差异信息更多，更有价值。就像“二八准则”，仅有20%的变因操纵着80%的局面，PCA中也一样。于是当我们从头到尾观察每个PC的差异贡献时，会有一个明显的“落差”，“落差”之前的那些PC就是“二八准则”中20%的变因。

这个方法得到的PC数一般是比较少而精的，但我们能保证elbow近邻的那几个PCs没有感兴趣的生物差异信号吗？

### **3.2 利用技术噪音**

这个方法是：设置一个差异贡献阈值，将每个PC的贡献率加起来，直到达到这个阈值，保留其中的所有PC。例如设置80%，也就是保留能够解释80%差异的PCs。

看到这里可能会奇怪，这个阈值的设置，不是也很主观吗？为了不显得这么主观，我们通过计算数据有多少比例的差异与生物因素相关，从而得到这个阈值。

> 更准确的描述是：Threshold is defined as the ratio of the sum of the biological components to the sum of total variances

整个计算是利用`denoisePCA()`函数完成，下面利用10X PBMC来展示

```r
# 需要考虑技术噪音，通过参数technical指定
library(scran)
set.seed(111001001)
denoised.pbmc <- denoisePCA(sce.pbmc, technical=dec.pbmc, subset.row=top.pbmc)
ncol(reducedDim(denoised.pbmc))
## [1] 8
```

**一般来说，`denoisePCA()`这种方法得到的PC数比elbow计算得到的要多**，但是它在去除技术噪音的时候，并没有去除一些不感兴趣的生物噪音（例如transcriptional bursting）。它**默认得到的PC数也是在5-50之间**，取决于技术噪音与生物偏差的多少（例如：当技术误差占主导地位，就会得到很少的PC；当技术误差很小时，PC数又会非常多）

```r
set.seed(001001001)
denoised.zeisel <- denoisePCA(sce.zeisel, technical=dec.zeisel, 
    subset.row=top.zeisel)
ncol(reducedDim(denoised.zeisel))
## [1] 50 #这个数据就达到了PC数的天花板
```

另外，`modelGeneVarByPoisson()` 和 `modelGeneVarWithSpikes()` 是考虑了技术误差，它们与`denoisePCA`的配合效果会更好。而基于log-counts估计的模型`modelGeneVar()` ，与`denoisePCA`的结果中，PC数就会减少

```r
dec.pbmc2 <- modelGeneVar(sce.pbmc)
denoised.pbmc2 <- denoisePCA(sce.pbmc, technical=dec.pbmc2, subset.row=top.pbmc)
ncol(reducedDim(denoised.pbmc2))
## [1] 5
# 之前可是得到了8个PC
```

不论如何，想要更多的PCs（`denoisePCA`方法）还是保留更“精”的生物偏差（`elbow`方法），最终取决于自己的想法

### **3.3 利用细胞分群的推断**

简言之就是根据亚群的数量来估计PC的数量，比如scRNA数据有10种不同的细胞类型，同时还要考虑一些噪音的存在，因此可以估计PC约等于10。不过这个想法有些理想，因为细胞分群的情况我们一开始不可能知晓，因此可以先进行预聚类

```r
pcs <- reducedDim(sce.zeisel)
choices <- getClusteredPCs(pcs)
metadata(choices)$chosen
## [1] 17

plot(choices$n.pcs, choices$n.clusters,
    xlab="Number of PCs", ylab="Number of clusters")
abline(a=1, b=1, col="red")
abline(v=metadata(choices)$chosen, col="grey80", lty=2)
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-29-095517.png)

图中红线是cluster数量的最大理论值，灰线就是`getClusteredPCs()`建议的PC数，可以看到如果使用推荐的17个PCs，基本能得到18个cluster

不过这个方法基于的假设是：整个数据可以分出许多亚群，而它们也是体现了不同的生物学意义。**如果数据不能够分出这么多亚群，这种方法就不会得到太多的PCs，会导致信息丢失**（例如研究连续生物学过程——分化时，可能就不会产生许多亚群）

### **3.4 自定义PC数量**

如果之前获得了PC的数量，但还想再精简（比如之前得到了50个PC，现在可以根据自己需求来定义PC数量，比如取前20个）

**可以直接在PCA结果上修改**

```r
reducedDim(sce.zeisel, "PCA") <- reducedDim(sce.zeisel, "PCA")[,1:20]
ncol(reducedDim(sce.zeisel, "PCA"))
## [1] 20
```

**还可以新增一个PCA结果**

```r
# 赋值一个新的变量名
reducedDim(sce.zeisel, "PCA_20") <- reducedDim(sce.zeisel, "PCA")[,1:20]
reducedDimNames(sce.zeisel)
## [1] "PCA"    "IRLBA"  "PCA_20"
```

其实`runPCA()` 可以直接定义PC数，这个过程只是为了方便后期探索更多不同的组合，多设置几组PCs看看差异

## 4 了解非负矩阵分解

> 可以把它当成降维的高阶玩法

英文名是：Non-negative matrix factorization（NMF），非负矩阵分解 是一个用来替代主成分分析的方法。它利用两个低维的矩阵（W和H，一个代表基因，一个代表细胞）来估计整个表达矩阵，并且其中所有的数据都要求是非负。它的想法和PCA类似，都是“缩小”数据，NMF是利用小的矩阵来归纳大矩阵的主要特性，最终降噪、压缩数据。相比于PCA的坐标，NMF坐标更容易解释，因为更大的值表示相应因子中基因的表达量更高。

scRNA的数据使用NMF也是很常见的 ([Shao and Höfer 2017](https://pubmed.ncbi.nlm.nih.gov/27663498/); [Kotliar et al. 2019](https://pubmed.ncbi.nlm.nih.gov/31282856/)) ，因此数据正好符合要求（虽然稀疏矩阵存在大量的0，但没有负数，并且即使采用log转换，还是会使用`log(x+1)` 的方法）

> 比如有一个LIGER算法，就首先使用综合非负矩阵分解（iNMF）来学习低维空间，获得一组特异因子，之后根据因子的maximum factor loading构建邻域图，从而达到降维目的

使用起来也不麻烦，scater包中有一个`runNMF`的函数（也是基于`NNLM` 包）。它的计算结果存储在`reducedDims()`的`NMF`模块中，之后就可以直接应用于分群。 相比于PCA的优点是：可以通过找到基因相关的小矩阵（W）每个因子中表达量高的基因，然后通过这些基因推测相应的细胞，再看细胞相关的小矩阵（H）中相应的高表达细胞，从而将细胞与细胞类型对应起来

```r
# 运行很简单
set.seed(101001)
nmf.zeisel <- runNMF(sce.zeisel, ncomponents=10, subset_row=top.zeisel)

# 提取结果
nmf.out <- reducedDim(nmf.zeisel, "NMF")
nmf.basis <- attr(nmf.out, "basis")
colnames(nmf.out) <- colnames(nmf.basis) <- 1:10

# 画每个细胞与因子的关系
per.cell <- pheatmap::pheatmap(nmf.out, silent=TRUE, 
    main="By cell", show_rownames=FALSE,
    color=rev(viridis::magma(100)), cluster_cols=FALSE) 

# 画每个基因与因子的关系
per.gene <- pheatmap::pheatmap(nmf.basis, silent=TRUE, 
    main="By gene", cluster_cols=FALSE, show_rownames=FALSE,
    color=rev(viridis::magma(100)))

# 组合
gridExtra::grid.arrange(per.cell[[4]], per.gene[[4]], ncol=2)
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-30-035316.png)

横坐标是定义好的10个因子，首先检查每个因子对应的高表达基因，看看其中有没有认识的marker基因。基于背景知识，Mog是少突细胞的marker基因，Gad1是中间神经元的marker基因。如果我们看到某个因子中Mog高表达（右图），那么这个因子就可能表示少突细胞。再看左图的细胞与因子的关系，找到对应的因子中高表达的细胞，那么这些细胞可能就是少突细胞。

因此，NMF在细胞类型识别中应该非常广。不过常规的流程还是基于PCA（入门选手必备），NMF可以用在更复杂的下游分析中（中高阶玩家选配）

## 5 降维后的可视化

> 一般的可视化算法都会基于10-50个PCs

### **5.1 PCA结果可视化**

```r
plotReducedDim(sce.zeisel, dimred="PCA", colour_by="level1class")
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-30-044804.png)

因为**PCA是线性降维模型**，每个PC捕获的是高维空间中线性的差异，不**能够在前两个PC中压缩全部的维度**。上图就能看到，前两个PC对亚群的显示效果不太理想，很多都没有分开。如果说第一个PC表示各个亚群之间最大的差异，PC2就是第二大差异，但是剩下的差异呢？没有显现！

虽然也可以在一张图画多个PC，但还是不好解释；另外即使用了这个方法，也不能将一些亚群分开

```r
# 在一张图画多个PC
plotReducedDim(sce.zeisel, dimred="PCA", ncomponents=4,
    colour_by="level1class")
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-30-050831.png)

PCA的优点就是：可预测、数据结构不引入人为因素、对输入数据的微小差异比较灵敏

### **5.2 t-SNE**

t-SNE是由SNE(Stochastic Neighbor Embedding, SNE; [Hinton and Roweis, 2002](https://www.cs.toronto.edu/~fritz/absps/sne.pdf))发展而来。于2008年提出，全称是： t-stochastic neighbor embedding，可以说是现在scRNA分析的标准可视化方法。与PCA不同，它**不局限于线性变换**，也不需要精确地表示各个细胞群之间的距离。能在高维空间中直接捕获细胞非线性关系，**它的分群更灵活**，可以在更复杂细胞群体中区分亚群

（因此**tSNE的点不表示数据的远近**，相距较近的点也不意味着细胞相似）

```r
set.seed(00101001101)

# runTSNE()的结果也是存储在reducedDims()中
sce.zeisel <- runTSNE(sce.zeisel, dimred="PCA")
plotReducedDim(sce.zeisel, dimred="TSNE", colour_by="level1class")
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-30-051928.png)

不过，**tSNE的最大缺点是**：计算量很大。不过可以在`runtTSNE()`中使用参数`dimred="PCA"` 来缓解一下；

**缺点二**：一般需要运行多次才能得到想要的结果，并且如果想要重复之前的结果，需要设置随机种子，因为tsne每次对细胞映射的坐标都不同（可能第一次运行这一团细胞在左下角，下一次又跑到右上角去了）

**缺点三：** 参数`perplexity`的使用。perplexity的含义是：the number of close neighbors each point has。 这个参数一般会多调整几次

**关于tsne这个流行的算法，有必要了解一下：**

* **tsne的作者Laurens强调**，可以通过`t-SNE`的可视化图提出一些假设，但是不要用`t-SNE`来得出一些结论，想要验证你的想法，最好用一些其他的办法。
* t-SNE中集群之间的**距离并不表示相似度** ，同一个数据上运行`t-SNE`算法多次，很有可能得到多个不同“形态”的集群。但话说回来，真正有差异的群体之间，不管怎么变换形态，它们还是有差别
* 关于perplexity的使用：(默认值是30) 如果忽视了perplexity带来的影响，有的时候遇到`t-SNE`可视化效果不好时，对于问题无从下手。**perplexity表示了近邻的数量**，例如设perplexity为2，那么就很有可能得到很多两个一对的小集群，也就意味着：perplexity如果很小，那么分辨率更高。

  ![https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2019-08-09-035102.png](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2019-08-09-035102.png)
* 有的时候会出现同一集群被分为两半的情况，但群间的距离并不能说明什么，解决这个问题，只需要跑多次找出效果最好的就可以了

> 引用自： <https://bindog.github.io/blog/2018/07/31/t-sne-tips/> \
> 很好的tsne可视化：<https://distill.pub/2016/misread-tsne/c>

下面看看不同`perplexity`参数的影响：

```r
set.seed(100)
sce.zeisel <- runTSNE(sce.zeisel, dimred="PCA", perplexity=5)
out5 <- plotReducedDim(sce.zeisel, dimred="TSNE",
    colour_by="level1class") + ggtitle("perplexity = 5")

set.seed(100)
sce.zeisel <- runTSNE(sce.zeisel, dimred="PCA", perplexity=20)
out20 <- plotReducedDim(sce.zeisel, dimred="TSNE",
    colour_by="level1class") + ggtitle("perplexity = 20")

set.seed(100)
sce.zeisel <- runTSNE(sce.zeisel, dimred="PCA", perplexity=80)
out80 <- plotReducedDim(sce.zeisel, dimred="TSNE", 
    colour_by="level1class") + ggtitle("perplexity = 80")

multiplot(out5, out20, out80, cols=3)
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-30-053043.png)

看到，t-SNE的结果看上去更像是一幅“地图”，但正是因为这样更**容易引起误导**，让我们根据clusters的大小和位置产生一些联想。事实上，**上面的点不能说明什么问题**，t -SNE的算法会让原本稠密的点变膨胀，让原本稀疏的点变紧凑，**不可以用cluster的大小来衡量亚群的异质性。**&#x5B83;并没有义务帮我们保留cluster的相对位置，因此我们**不能根据点的位置远近来确定cluster之间的相似程度。**

### 5.3 UMAP

2018年提出[McInnes et al., (2018)](https://joss.theoj.org/papers/10.21105/joss.00861)，全称是：Uniform manifold approximation and projection，与t-SNE一样，也是一种非线性方法，都是在高维空间中寻找保持相邻关系的低维表示方法。不过它们的基础理论不同，因此图形展示也不同

```r
set.seed(1100101001)
sce.zeisel <- runUMAP(sce.zeisel, dimred="PCA")
plotReducedDim(sce.zeisel, dimred="UMAP", colour_by="level1class")
```

![](https://jieandze1314-1255603621.cos.ap-guangzhou.myqcloud.com/blog/2020-06-30-054144.png)

可以看到，**UMAP相比t-SNE，各个cluster更加紧凑，而且cluster之间的距离空间也更大**。UMAP比t-SNE保留了**更多的空间结构**。**运算速度也更快**，因此对于大型数据是个福音（尽管如此，还是推荐使用前几个PC进行UMAP）。另外它也需要指定随机种子。

**劣势一：**

UMAP也有自己的**复杂参数设定**，例如近邻的数量`n_neighbors` 和 点之间最短距离`min_dist` 都会结果展示都比较大的影响。如果这些值太小，随机噪音就会显现出来；如果设置太大，就会丢弃一些原本不错的数据结构。所以**建议也是：多试几组参数的设置**

**劣势二：**

UMAP旨在保留更多的全局结构，但这必然会降低每个cluster中的分辨率。

### **5.4 最后，作者对于可视化的建议**

Do not let the tail (of visualization) wag the dog (of quantitative analysis)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jieandze1314.osca.top/03/03-4.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
