17 Кластеризация

library(tidyverse)

Кластеризация — это не метод, а задача, для решение которой придумано множество алгоритмов. Не существует “правильных”методов кластеризации, так как “clustering is in the eye of the beholder”[Estivill-Castro 2002]. Мы рассмотрим два семейства алгоритмов:

  • метод k-средних (k-means)
  • иерархическая кластеризация (hierarchical clustering)

17.1 Метод k-средних (k-means)

Алгоритм k-means был разработан в статье [Lloyd 1982]:

  • на вход алгоритму подаются данные и k — количество кластеров, на которые эти данные надо поделить;
  • произвольно выбираются k точек (центроидов) и рассчитываются ближайшие расстояния (евклидово) от данных точек до центроидов, точки которые ближе всего к некоторому центроиду образуют кластер;
  • на основе точек вошедших в кластер строится новый центроид, так чтобы расстояние от всех точек до нового центроида было минимально;
  • часть точек становится ближе к новому центроиду и входят в его кластер, а часть от центроида отдаляется и начинают входить в другой/другие кластер/кластеры;
  • … все это повторяется, пока на некоторой итерации не происходит изменение положения центроидов.

Давайте посмотрим визуализацию алгоритма k-средних, которую сделал Нафтали Харрис.

17.1.1 Пример

Давайте проанализируем данные из датасета iris:

iris %>% 
  ggplot(aes(Petal.Length, Petal.Width, color = Species))+
  geom_point()

Для того чтобы запустить метод k-средних в R нужно использовать функцию kmeans(), указав количество кластеров в centers:

library(broom)
set.seed(42)
iris %>% 
  select(Petal.Length, Petal.Width) %>% 
  kmeans(centers = 3) ->
  km

Давайте посмотрим, насколько хорошо алгоритм k-средних справился с заданием. Я воспользуюсь функцией augment() из пакета broom, чтобы добавить результаты модели к исходным данным (это работает и с регрессиями)

km %>% 
  augment(iris) %>% 
  count(Species, .cluster)

Мы видим, что алгоритм все разбил на три кластера (1, 2, 3), 1 соответствует setosa, 2 соответствует versicolor, 3 соответсвтует virginica (я смотрю с какой группой ассоциировано наибольшое n). Я использую функцию recode_factor() для того чтобы перекодировать переменную .cluster:

km %>% 
  augment(iris) %>% 
  mutate(.cluster = recode_factor(.cluster,
                                  `1` = "setosa", 
                                  `2` = "versicolor",
                                  `3` = "virginica"),
         correct = Species == .cluster) %>% 
  ggplot(aes(Petal.Length, Petal.Width))+
  geom_point(aes(color = correct, shape = Species))+
  geom_point(data = data.frame(km$centers)) # центроиды

Цветом выделены несовпадения с исходными данными, как видно, таких случаев всего 5: два цветка virginica были отнесены к классу versicolor, три цветка virginica были отнесены к versicolor. Так что в целом, можно сказать, что алгоритм хорошо справился. Черным обозначены центроиды получившихся кластеров.

17.2 Иерархическая кластеризация

Иерархические кластеризации имеют два типа:

  • снизу вверх (agglomerative): каждое наблюдение в начальной позиции является кластером, дальше два ближних кластера соединяются в один, а дендограмма отображает порядки таких соединений.
  • сверху вниз (divisive): все наблюдения в начальной позиции являются кластером, который дальше делится на более мелкие, а дендограмма отображает порядки таких разъединений. Алгоритмы иерархической кластеризации требуют на вход матрицы расстояний. Алгоритмов кластерного анализа очень много, так что имеет смысл заглянуть в работу [Gordon 1987] и на страницу CRAN.

17.2.1 Матрица расстояний

Матрица расстояний — это матрица n × n, которая содержит значения меры расстояния/сходства между объектами в метрическом пространстве. Существует уйма мер расстояния/сходства, выбор из которых зависит от типа данных. К сожалению, не существует универсального алгоритма выбора метода, так что это остается на откуп исследователям. Кроме того, схожие методы, зародившиеся в биологии, называют string metric: они определяют расстояния между строками (расстояние Хэмминга, расстояние Левинштейна и т. п.)

17.2.1.1 Бинарные данные

Представим вот такие данные для нескольких языков:

df <- tibble(lang = c("Lithuanian", "Latvian", "Prussian", "ChurchSlavonic"),
             word_1 = c(1, 1, 1, 0),
             word_2 = c(1, 1, 1, 0),
             word_3 = c(1, 1, 0, 0),
             word_4 = c(1, 0, 0, 0),
             word_5 = c(0, 0, 0, 1))
df

Существует множество мер для анализа бинарных данных. Самый распространенный — коэффициент Жаккара. Для каждой пары идиомов строим вот такую таблицу:

идиом i
1 0
идиом j 1 a b
0 c d

А дальше мы считаем меру сходства:

\[s(i, j)=\frac{a}{a+b+c}\]

В работе [Gower and Legendre 1986] есть и другие методы (14 шт.). Большинство из них есть в функции dist.binary() пакета ade4.

Дальше можно использовать функцию dist() с аргументом binary. Я использую функцию tidy() из пакета broom, чтобы получить таблицу:

df %>%
  select(-lang) %>% 
  dist(method = "binary") %>% 
  tidy()

Можно визуализировать матрицу расстояния:

df %>%
  select(-lang) %>% 
  dist(method = "binary") %>% 
  tidy() %>% 
mutate(item1 =recode_factor(item1, 
                            `1` = "Lithuanian", 
                            `2` = "Latvian", 
                            `3` = "Prussian",
                            `4` = "ChurchSlavonic"),
       item2 =recode_factor(item2, 
                            `1` = "Lithuanian", 
                            `2` = "Latvian", 
                            `3` = "Prussian",
                            `4` = "ChurchSlavonic")) %>% 
  ggplot(aes(item1, item2, fill=distance, label = round(distance, 2))) +
  geom_tile()+
  geom_text(color = "white")

17.2.1.2 Числовые переменные

iris %>% 
  select(Petal.Length, Petal.Width) %>% 
  dist() %>% 
  tidy() %>% 
  ggplot(aes(item1, item2, fill=distance)) +
  geom_tile()

Расстояние здесь сложнее интерпретировать, потому что на графике представлено расстояние между 150 цветками.

17.2.2 Расстояние между словами

Мы уже обсуждали расстояние между словами, его тоже можно использовать для кластеризации:

library(stringdist)
stringdistmatrix(c("корова", "коровы", "гуси")) 
  1 2
2 1  
3 6 6

17.2.3 Применение иерархической кластеризации

iris %>% 
  select(Petal.Length, Petal.Width) %>% 
  dist() %>% 
  hclust() ->
  hc

Объект иерархической кластеризации легко визуализировать:

plot(hc)

Также можно выделить какое-то количество кластеров:

rect.hclust(hc, k=3)

Функция cutree() возвращает вектор номеров кластеров в соответсвтии с данными, так что можно строить все предыдущие графики:

iris %>% 
  mutate(cluster = cutree(hc, k=3)) %>% 
  count(Species, cluster)

Мы видим, что алгоритм все разбил на три кластера (1, 2, 3), 1 соответствует setosa, 2 соответствует virginica и versicolor, 3 соответсвтует versicolor (я смотрю с какой группой ассоциировано наибольшое n). Я использую функцию recode_factor() для того чтобы перекодировать переменную .cluster:

iris %>% 
  mutate(cluster = cutree(hc, k=3)) %>% 
  mutate(cluster = recode_factor(cluster,
                                 `1` = "setosa", 
                                 `2` = "virginica",
                                 `3` = "versicolor"),
         correct = Species == cluster) %>% 
  ggplot(aes(Petal.Length, Petal.Width))+
  geom_point(aes(color = correct, shape = Species))

Мы видим, что ошибки в осовном сгруппированы на границе двух кластеров (видимо, точек меньше, чем 21 потому что они совпадают).