为基因表达数据选择距离度量和聚类方法的最佳实践

机器算法验证 r 聚类 距离 层次聚类 生物信息学
2022-03-23 06:01:23

我一直在各种渠道(包括此处和 Stack Exchange)上阅读此内容,但我仍然不确定如何选择对基因表达数据进行聚类的最佳方法。作为博士 分子生物学家(没有深入的数学/统计学背景),我正在寻找一组应该遵循的聚类指南。我将为我的问题设置以下阶段并提供一个可重复的示例,但作为背景研究,我做了以下并没有真正帮助的操作:

  • 我在 SE/SO 中进行了广泛的搜索,并就这个问题与各种生物信息学家进行了交谈。hclust我了解各种方法和distance指标之间的一般差异。虽然我意识到我的问题听起来很常见,但我无法找到令人满意的答案来理解对 RNAseq 和微阵列数据进行聚类的最佳方法。似乎很多人都有他们最喜欢的做事“方式”,并且没有太多思考去理解应该使用哪个distance metric/clustering method应该使用以及为什么。

  • 我阅读了几篇关于聚类方法选择的文章,包括thisthisthisthis和许多其他的

我的目标是根据样本的基因表达谱对样本进行聚类,并在数据集中找到真实的模式其次,我还想对基因(列中的变量)进行层次聚类分析。

关于数据结构的几句话:像许多常见的 RNAseq 数据一样,我的真实 RNAseq 数据集由数百个观察结果(行中的样本)和数千个基因(列中的变量)组成。样本间基因表达值的分布可能与正常相似,也可能不相似,并且表达范围可能有很大差异。通过使用已建立的方法(例如limmaDEseq2),我生成了 log2 尺度的归一化计数(基于转录总数的归一化)。我想通过使用整个数据集和我感兴趣的基因子集来执行聚类。

我在下面有一个冗长的可重复示例,请看一下我的问题(尤其是最后的比较是相关的)。

我的具体问题是:

对样本(观察)进行聚类的最合适的距离度量和层次聚类方法是什么?为什么?我在下面对模拟数据 ( ) 执行hclust了不同的操作,结果变化很大。请参阅聚类方法之间的聚类树比较和总体。我不确定该相信哪一个。methodsmtxcorrelation

很抱歉这篇长文,但总而言之,我试图了解最合适的基因表达数据聚类方法(适用于 RNAseq 和微阵列)以查看真实模式,同时避免由于随机机会而可能出现的模式。

可重现的例子

模拟数据

library(reprex)
library(pheatmap)
library(dendextend)
library(factoextra)
library(corrplot)
library(dplyr)



set.seed(123)

mtx_dims <- c(30, 500)

mtx <- matrix(rnorm(n = mtx_dims[1]*mtx_dims[2], mean = 0, sd = 4), nrow = mtx_dims[1])

mtx[, 1:10] <- mtx[ , 1:10] + 10  # blow some genes off-scale
mtx[, 11:20] <- mtx[, 11:20] + 20 
mtx[, 21:30] <- mtx[, 11:20] + 30 
mtx[, 31:40] <- mtx[, 11:20] + 40 
mtx[, 41:50] <- mtx[, 11:20] + 50 


rownames(mtx) <- paste0("sample_", 1:mtx_dims[1])
colnames(mtx) <- paste0("gene_", 1:mtx_dims[2])

rowannot <- data.frame(sample_group = sample(LETTERS[1:3], size = mtx_dims[1], replace = T))
rownames(rowannot) <- rownames(mtx)


unscaled_mtx <- mtx

mtx <- scale(mtx)

探索性热图/聚类

pheatmap(mtx,
         scale = "none",
         clustering_distance_rows = "euclidean",
         clustering_distance_cols = "euclidean",
         clustering_method = "complete",
         main = "Euclidean distance (hclust method: complete)",
         annotation_row = rowannot,
         show_colnames = F)


pheatmap(mtx,
         scale = "none",
         clustering_distance_rows = "correlation",
         clustering_distance_cols = "correlation",
         clustering_method = "complete",
         main = "Correlation distance (hclust method: complete)",
         annotation_row = rowannot,
         show_colnames = F)

pheatmap(unscaled_mtx,
         scale = "none",
         clustering_distance_rows = "euclidean",
         clustering_distance_cols = "euclidean",
         clustering_method = "complete",
         main = "(Unscaled data) Euclidean distance (hclust method: complete)",
         annotation_row = rowannot,
         show_colnames = F)

规模化的 mtx 集群

欧几里得距离
d_euc_mtx <- dist(mtx, method = "euclidean")    


hclust_methods <- c("ward.D", "single", "complete", "average", "mcquitty", 
                    "median", "centroid", "ward.D2")

mtx_dendlist_euc <- dendlist()

for(i in seq_along(hclust_methods)) {


    hc_mtx <- hclust(d_euc_mtx, method = hclust_methods[i])   

    mtx_dendlist_euc <- dendlist(mtx_dendlist_euc, as.dendrogram(hc_mtx))
}

names(mtx_dendlist_euc) <- hclust_methods


mtx_dendlist_euc_cor <- cor.dendlist(mtx_dendlist_euc, method_coef = "spearman")


corrplot(mtx_dendlist_euc_cor, "pie", "lower")
mtx_dendlist_euc %>% dendlist(which = c(1,3)) %>% ladderize %>% 
    set("branches_k_color", k=3) %>% 
    tanglegram(faster = TRUE)

皮尔逊相关距离
d_cor_mtx <- get_dist(mtx, method= "pearson", diag=T, upper=T)



mtx_dendlist_cor <- dendlist()

for(i in seq_along(hclust_methods)) {

    hc_mtx <- hclust(d_cor_mtx, method = hclust_methods[i])   

    mtx_dendlist_cor <- dendlist(mtx_dendlist_cor, as.dendrogram(hc_mtx))
}

names(mtx_dendlist_cor) <- hclust_methods

mtx_dendlist_cor_cor <- cor.dendlist(mtx_dendlist_cor, method_coef = "spearman")


corrplot(mtx_dendlist_cor_cor, "pie", "lower")
mtx_dendlist_cor %>% dendlist(which = c(1,3)) %>% ladderize %>% 
    set("branches_k_color", k=3) %>% 
    tanglegram(faster = TRUE)

未缩放的 mtx 集群

欧几里得距离
d_euc_mtx <- dist(unscaled_mtx, method = "euclidean")    


hclust_methods <- c("ward.D", "single", "complete", "average", "mcquitty", 
                    "median", "centroid", "ward.D2")

mtx_dendlist_euc <- dendlist()

for(i in seq_along(hclust_methods)) {


    hc_mtx <- hclust(d_euc_mtx, method = hclust_methods[i])   

    mtx_dendlist_euc <- dendlist(mtx_dendlist_euc, as.dendrogram(hc_mtx))
}

names(mtx_dendlist_euc) <- hclust_methods




mtx_dendlist_euc_cor <- cor.dendlist(mtx_dendlist_euc, method_coef = "spearman")


corrplot(mtx_dendlist_euc_cor, "pie", "lower")
mtx_dendlist_euc %>% dendlist(which = c(1,3)) %>% ladderize %>% 
    set("branches_k_color", k=3) %>% 
    tanglegram(faster = TRUE)

皮尔逊相关距离
d_cor_mtx <- get_dist(unscaled_mtx, method= "pearson", diag=T, upper=T)


mtx_dendlist_cor <- dendlist()

for(i in seq_along(hclust_methods)) {

    hc_mtx <- hclust(d_cor_mtx, method = hclust_methods[i])   

    mtx_dendlist_cor <- dendlist(mtx_dendlist_cor, as.dendrogram(hc_mtx))
}

names(mtx_dendlist_cor) <- hclust_methods


mtx_dendlist_cor_cor <- cor.dendlist(mtx_dendlist_cor, method_coef = "spearman")


corrplot(mtx_dendlist_cor_cor, "pie", "lower")
mtx_dendlist_cor %>% dendlist(which = c(1,3)) %>% ladderize %>% 
    set("branches_k_color", k=3) %>% 
    tanglegram(faster = TRUE)

集群验证(使用缩放矩阵)

# The goal of this is to understand how many clusters are predicted by different
# clustering methods and index scores.

suppressPackageStartupMessages(library(NbClust))


indices <- c("kl", "ch", 
             # "hubert", "dindex",  # take longer to compute and create graphical outputs
             "ccc", "scott", "marriot", "trcovw", 
             "tracew", "friedman", "rubin", "cindex", 
             "db", "silhouette", "duda", "pseudot2", 
             "beale", "ratkowsky", "ball", "ptbiserial", 
             "gap", "frey", "mcclain", "gamma", "gplus", 
             "tau", "dunn","hartigan", "sdindex",  "sdbw")

cl_methods_nb <- c("ward.D", "ward.D2", "single", "complete", "average", "mcquitty", "median", "centroid", "kmeans")

val_res <- list()

for(j in cl_methods_nb){

    for(i in indices) {

        # message(i)

        tryCatch({
            val_res[[paste(j,i, sep = "_")]] <- NbClust(data = mtx, diss = d_cor_mtx, 
                                                        distance = NULL, method = j,
                                                        index=i, max.nc = 6)}, 
            error=function(e){
                # message(paste(j, i, "failed"))
            })

    }

}
#> Warning in pf(beale, pp, df2): NaNs produced

#> Warning in pf(beale, pp, df2): NaNs produced
#> [1] "Frey index : No clustering structure in this data set"
#> [1] "Frey index : No clustering structure in this data set"



val_res_nc <- data.frame()

for(i in names(val_res)){

    method_name <- gsub("_.*", "", i)
    index_name <- gsub(".*_", "", i)

    if(!"Best.nc" %in% names(val_res[[i]])) next

    df_int <- data.frame(method_name = method_name,
                         index_name = index_name,
                         best_nc = val_res[[i]][["Best.nc"]][1])

    val_res_nc <- rbind(val_res_nc, df_int)

}


# Breakdown of cluster number as predicted various clustering
# methods and validation indices
summary(as.factor(val_res_nc$best_nc))
#>  1  2  3  4  5  6 
#>  3 71 20  9 21 63

# Tabulate data
head(
    val_res_nc %>%
         group_by(method_name, index_name) %>%
         summarize(best_nc), 10
    )
#> # A tibble: 10 x 3
#> # Groups:   method_name [1]
#>    method_name index_name best_nc
#>    <fct>       <fct>        <dbl>
#>  1 ward.D      kl               4
#>  2 ward.D      ch               2
#>  3 ward.D      cindex           6
#>  4 ward.D      db               6
#>  5 ward.D      silhouette       6
#>  6 ward.D      duda             5
#>  7 ward.D      pseudot2         5
#>  8 ward.D      beale            5
#>  9 ward.D      ratkowsky        6
#> 10 ward.D      ball             3

hclust 方法之间的相关性

比较聚类方法

1个回答

这可能不是您想要或期望的答案,但这就是我对这些事情的看法。

聚类问题

在某种程度上,聚类几乎总是一个主观的过程。您决定如何将不同元素组合在一起,然后选择满足您愿望的距离度量,然后按照程序进行。

这是一个简短的示例 - 假设我们要将这些动物分组:

动物

我们可以尝试不同的距离(基于它们有多少条腿、它们是否会游泳、它们有多高、它们的颜色),所有的指标都会给出不同的聚类。我们能说其中一些是正确的,而另一些是错误的吗?不。“我应该相信哪个结果”的问题有意义吗?也没有。

RNA表达数据

你的例子也发生了同样的事情。

想象一下,您想将不同的基因分组。马上就会出现问题:

1)关于距离测量的问题:显示相同模式但整体表达水平不同的基因应该进入同一组(基于相关的距离)还是不同的组(基于差异的距离)?图案是不是整体表达水平更重要?如果两个基因是反相关的,这是否意味着它们是相关的并且在同一组中,或者在不同的组中(符号是否重要)?更大的偏差应该受到更多的“惩罚”(欧几里得距离),还是所有差异的大小都同等重要(曼哈顿距离)?

2)关于联动功能的问题:我是否希望一组内的所有元素最多相隔“X”距离(完全联动)?或者,如果存在从一个配置文件到另一个配置文件的一连串小变化(单链接),我是否想将基因分组在同一个集群下?等等

这些是从业者必须回答的问题,以便获得他以后可以解释的合理结果。上述所有选项背后都可能具有生物学意义。在一种情况下,你会得到一组表现出相似表达水平的基因,在另一种情况下——一组表现出相似趋势的基因。没有一种方法可以做到这一点,也没有理由认为您应该相信一个结果并怀疑其他结果。这可能听起来陈词滥调,但从某种意义上说,在他开始做之前必须知道他或她想做什么。

我认为看待这个问题的正确方法是,人们应该在一种情况下更喜欢一种方法,而在不同情况下更喜欢另一种方法。

一些可能性

现在让我们假设我们关心以下事情:

  1. 如果基因是线性相关的(在同一个体中增加或减少),我们希望对它们进行分组。
  2. 我们不关心两个基因之间的大小差异(因为它们可以在不同的水平上表达,但仍然是相关的)。

满足上述条件的一种可能性是使用绝对相关水平作为距离:1|cor(gene1,gene2)|.

然后在我们创建了我们想要的树状图之后:

  1. 创建组,使组内的所有元素相互关联至少|0.7|。

为此,我们将选择“完整”链接并在 0.3 的高度处切割树(请记住距离是 1 减去相关值)。

问题和建议

现在有了上述背景,以下是问题的答案:

对样本(观察)进行聚类的最合适的距离度量和层次聚类方法是什么?为什么?

最合适的距离将视情况而定。如果你想通过它们的整体表达对样本/基因进行分组——你必须使用一个距离。如果您想按模式对它们进行分组 - 另一个距离。

我在模拟数据(mtx)上使用以下不同方法执行了 hclust,结果变化很大。我不确定该相信哪一个。

他们大多都同样可信。由于他们都试图实现略有不同的东西,因此获得的结果也不同。

我试图了解对基因表达数据进行聚类的最合适方法(适用于 RNAseq 和微阵列)以查看真实模式,同时避免由于随机机会而可能出现的模式。

避免由于偶然或更糟糕的技术原因(即样品是分批完成的)而出现的模式并不容易。

对于噪音,我建议不要扩展您的特征(基因)。缩放会将真实信号和噪声带到相同的水平,这可能会对结果产生影响。

对于技术部分 - 我会确保通过聚类程序获得的组不遵循某些技术参数的模式(即在 batch1 上完成的样本在一个集群中,在 batch2 上完成的样本在另一个集群中)。如果您发现是这种情况,那么这种批次效应可能会对样本集群和基因集群产生巨大影响。

您可能会尝试的另一件事(例如,在对基因进行聚类时)是寻找聚类背后的生物学意义。如果您发现一个集群中的基因具有一些常见的本体术语,这些术语可能会提供额外的信心,即您发现的集群是有意义的,而不仅仅是噪音。

最后,您似乎想尝试仅使用显示某些组之间差异的基因进行聚类。这是一个毫无意义的练习(在我看来),因为很清楚结果会是什么样子:即使该过程是在随机生成的数字上执行的,您要比较的两个组也必然是分开的。