R получение поддеревьев из дендрограммы на основе меток разрезов

Я сгруппировал большой набор данных и нашел 6 кластеров, которые мне интересно проанализировать более подробно.

Я нашел кластеры, используя hclust с методом "ward.D", и хотел бы знать, есть ли способ получить "поддеревья" из объектов hclust / dendrogram.

Например

library(gplots)
library(dendextend)

data <- iris[,1:4]
distance <- dist(data, method = "euclidean", diag = FALSE, upper = FALSE)
hc <- hclust(distance, method = 'ward.D')
dnd <- as.dendrogram(hc)
plot(dnd) # to decide the number of clusters
clusters <- cutree(dnd, k = 6)

Я использовал cutree, чтобы получить метки для каждой строки в моем наборе данных.

Я знаю, что могу получить данные для каждого соответствующего кластера (например, кластера 1) с помощью:

c1_data = data[clusters == 1,]

Есть ли простой способ получить поддеревья для каждой соответствующей метки, возвращенные dendextend::cutree? Например, скажем, мне интересно получить

Я знаю, что могу получить доступ к ветвям дендрограммы, выполнив что-то вроде

subtree <- dnd[[1]][[2]

но как я могу получить именно поддерево, соответствующее кластеру 1?

я пытался

dnd[clusters == 1]

но это, конечно, не работает. Итак, как я могу получить поддерево на основе меток, возвращаемых cutree?


person lucacerone    schedule 09.01.2018    source источник


Ответы (3)


================= ОБНОВЛЕННЫЙ ответ

Теперь это можно решить с помощью get_subdendrograms из dendextend.

# needed packages:
# install.packages(gplots)
# install.packages(viridis)
# install.packages(devtools)
# devtools::install_github('talgalili/dendextend') # dendextend from github

# define dendrogram object to play with:
dend <- iris[,-5] %>% dist %>% hclust %>% as.dendrogram %>%  set("labels_to_character") %>% color_branches(k=5)
dend_list <- get_subdendrograms(dend, 5)

# Plotting the result
par(mfrow = c(2,3))
plot(dend, main = "Original dendrogram")
sapply(dend_list, plot)

введите описание изображения здесь

Это также можно использовать в тепловой карте:

# plot a heatmap of only one of the sub dendrograms
par(mfrow = c(1,1))
library(gplots)
sub_dend <- dend_list[[1]] # get the sub dendrogram
# make sure of the size of the dend
nleaves(sub_dend)
length(order.dendrogram(sub_dend))
# get the subset of the data
subset_iris <- as.matrix(iris[order.dendrogram(sub_dend),-5])
# update the dendrogram's internal order so to not cause an error in heatmap.2
order.dendrogram(sub_dend) <- rank(order.dendrogram(sub_dend))
heatmap.2(subset_iris, Rowv = sub_dend, trace = "none", col = viridis::viridis(100))

введите описание изображения здесь

================= СТАРЫЙ ответ

Я думаю, что вам могут быть полезны следующие две функции:

Первый просто перебирает все кластеры и извлекает подструктуру. Это требует:

  • объект dendrogram, из которого мы хотим получить субдендрограммы
  • метки кластеров (например, возвращенные cutree)

Возвращает список субдендрограмм.

extractDendrograms <- function(dendr, clusters){
    lapply(unique(clusters), function(clust.id){
        getSubDendrogram(dendr, which(clusters==clust.id))
    })
}

Второй выполняет поиск в глубину, чтобы определить, в каком поддереве существует кластер, и если он соответствует полному кластеру, возвращает его. Здесь мы используем предположение, что все элементы кластера находятся в одном подгруппе. Это требует:

  • объект дендрограммы
  • позиции элементов в кластере

Возвращает субдендрограммы, соответствующие кластеру заданных элементов.

getSubDendrogram<-function(dendr, my.clust){
    if(all(unlist(dendr) %in% my.clust))
        return(dendr)
    if(any(unlist(dendr[[1]]) %in% my.clust ))
        return(getSubDendrogram(dendr[[1]], my.clust))
    else 
        return(getSubDendrogram(dendr[[2]], my.clust))
}

Используя эти две функции, мы можем использовать переменные, которые вы указали в вопросе, и получить следующий результат. (Думаю, строчка clusters <- cutree(dnd, k = 6) должна быть clusters <- cutree(hc, k = 6))

my.sub.dendrograms <- extractDendrograms(dnd, clusters)

построение всех шести элементов из списка дает все субдендрограммы

введите описание изображения здесь

ИЗМЕНИТЬ

Как предлагается в комментарии, я добавляю функцию, которая в качестве входных данных принимает дендрограмму dend и количество поддеревьев k, но по-прежнему использует ранее определенную рекурсивную функцию getSubDendrogram:

prune_cutree_to_dendlist <- function(dend, k, order_clusters_as_data=FALSE) {
    clusters <- cutree(dend, k, order_clusters_as_data)
    lapply(unique(clusters), function(clust.id){    
        getSubDendrogram(dend, which(clusters==clust.id))
    })
}

Тестовый пример для 5 подструктур:

library(dendextend)
dend <- iris[,-5] %>% dist %>% hclust %>% as.dendrogram %>% set("labels_to_character") %>% color_branches(k=5)

subdend.list <- prune_cutree_to_dendlist(dend, 5)

#plotting
par(mfrow = c(2,3))
plot(dend, main = "original dend")
sapply(prunned_dends, plot)

Я выполнил несколько тестов, используя rbenchmark с функцией, предложенной Талом Галили (здесь она называется prune_cutree_to_dendlist2), и результаты довольно многообещающие для подхода DFS из приведенного выше:

library(rbenchmark)
benchmark(prune_cutree_to_dendlist(dend, 5), 
          prune_cutree_to_dendlist2(dend, 5), replications=5)

                                test replications elapsed relative user.self
1  prune_cutree_to_dendlist(dend, 5)            5    0.02        1     0.020
2 prune_cutree_to_dendlist2(dend, 5)            5   60.82     3041    60.643

введите описание изображения здесь

person storaged    schedule 09.01.2018
comment
Привет, @storaged. Я пробовал запустить ваш код, но у меня ничего не вышло. Можете ли вы создать одну функцию, которая получает дендрограмму и k и возвращает список с k субдендрограммами? Если да, то мы можем сравнить это с моим кодом. Если он сработает (и будет быстрее), я был бы рад, если бы вы добавили его в dendextened с помощью запроса на перенос (github.com/talgalili/dendextend/issues/61) - person Tal Galili; 10.01.2018
comment
@TalGalili, ваше предложение было действительно интересным. А пока я добавил функцию-оболочку, которая использует подфункции из первой версии сообщения. У меня это работает, как показано в тестовом примере. Я могу попробовать сделать несколько тестов времени, если вам действительно интересно? - person storaged; 10.01.2018
comment
Я также добавил тест. Кажется, что подход DFS работает очень хорошо :) Буду очень признателен, если вы подтвердите, что код работает на вас сейчас. - person storaged; 10.01.2018
comment
Привет, @storaged. Наконец-то мне удалось взглянуть на ваш код, и для меня совершенно очевидно, что он работает так быстро по сравнению с предложенным мною решением. Я думаю, что это нужно сделать для dendextend. Вы знаете, как сделать форк и запрос на опрос? - person Tal Galili; 11.01.2018
comment
@TalGalili Я сделал запрос вилки / вытягивания, вы можете проверить, все ли в порядке. Я также попытался предоставить немного документации. Я очень рада, что это помогло! - person storaged; 11.01.2018
comment
@storaged Tal любезно предложил отметить ваше решение как правильное. Я не сделал этого, потому что по какой-то причине это не сработало, когда я попытался, но я действительно с нетерпением жду возможности использовать его от dendextend! Спасибо вам обоим за вашу помощь! - person lucacerone; 12.01.2018
comment
Привет, @lucacerone. Теперь я объединил сохраненные ответы / функции в dendextend и обновил этот вопрос решением (включая добавление тепловой карты.2). Эта функция должна (надеюсь) быть достаточно быстрой, чтобы иметь дело с вашими данными. Ваше здоровье - person Tal Galili; 19.01.2018

Я написал функцию prune_cutree_to_dendlist, чтобы сделать то, о чем вы просили. Я должен добавить его в dendextend в будущем.

А пока вот пример кода и вывода (функция немного медленная. Чтобы сделать ее быстрее, нужно, чтобы prune была быстрее, и я не собираюсь исправлять ее в ближайшем будущем.)

# install.packages("dendextend")

library(dendextend)
dend <- iris[,-5] %>% dist %>% hclust %>% as.dendrogram %>% 
  set("labels_to_character")
dend <- dend %>% color_branches(k=5)

# plot(dend)

prune_cutree_to_dendlist <- function(dend, k) {
  clusters <- cutree(dend,k, order_clusters_as_data = FALSE)
  # unique_clusters <- unique(clusters) # could also be 1:k but it would be less robust
  # k <- length(unique_clusters)
  # for(i in unique_clusters) { 
  dends <- vector("list", k)
  for(i in 1:k) { 
    leves_to_prune <- labels(dend)[clusters != i]
    dends[[i]] <- prune(dend, leves_to_prune)

  }

  class(dends) <- "dendlist"

  dends
}

prunned_dends <- prune_cutree_to_dendlist(dend, 5)
sapply(prunned_dends, nleaves)

par(mfrow = c(2,3))
plot(dend, main = "original dend")
sapply(prunned_dends, plot)

введите описание изображения здесь

person Tal Galili    schedule 10.01.2018
comment
Таль, спасибо тебе огромное! Я задавался вопросом, почему этого уже нет в dendextend и думал сделать пиар, но вы меня опередили вовремя! Большое спасибо! Я воспользуюсь вашей функцией и очень жду, когда она будет интегрирована в dendextend! пс. есть ли какая-нибудь виньетка на внутреннем устройстве dendextend, что-то вроде учебника по расширенному dendextend? - person lucacerone; 10.01.2018
comment
Привет, люкацерон, я думаю, что хранимый раствор должен быть отмечен как правильный раствор. Хотя и мое решение, и его решения верны, его решение более адаптировано к проблеме и позволит НАМНОГО быстрее время отклика. Я добавлю его функцию (с некоторыми изменениями) в dendextend. Я также отредактирую его ответ, когда он будет готов (но я думаю, что он заработал своих очков кармы больше, чем я :)) - person Tal Galili; 11.01.2018
comment
Я хотел бы поблагодарить вас обоих за то, что помогли мне с этим :) по какой-то причине я не смог запустить сохраненное решение, в то время как ваше работает при первом запуске (но, как вы говорите, это довольно медленно, и в конце я решил пропустить этот шаг в моем анализе) .. с примерно ~ 20000 строками для кластера я не мог получить результаты через 15 минут ... Еще раз спасибо за помощь и за честную игру, тогда я отмечу его решение :) - person lucacerone; 12.01.2018

Как вы получили 6 кластеров с помощью hclust? Вы можете разрезать дерево в любой момент, поэтому просто попросите cuttree дать вам больше кластеров:

clusters = cutree(hclusters, number_of_clusters)

Однако, если у вас много данных, это может быть не очень удобно. В этих случаях я вручную выбираю кластеры, которые хочу изучить дальше, а затем запускаю hclust только для данных в этих кластерах. Я не знаю ни одной функции в hclust, которая позволяет делать это автоматически, но это довольно просто:

good_clusters = c(which(clusters==1), 
                  which(clusters==2)) #or whichever cLusters you want
new_df = df[good_clusters,]
new_hclusters = hclust(new_df)
new_clusters = cutree(new_hclusters, new_number_of_clusters)
person Eudald    schedule 09.01.2018
comment
Спасибо за ответ @eudald. Я изменил вопрос и добавил код, надеясь, что теперь стало яснее, чего я хочу достичь. Ваше здоровье! - person lucacerone; 09.01.2018
comment
См. Функцию сокращения в dendextend. - person Tal Galili; 10.01.2018