В этой статье я сделаю проект на Python о темах обучения без присмотра, которыми я ранее делился теоретической информацией и приложениями с R в сериях. Это будет подробный проект неконтролируемого обучения с набором данных Рак груди, Висконсин из репозитория машинного обучения UCI, который я использовал в статьях, в которых делился теоретической информацией.

Для блокнота Jupyter вы можете увидеть этот репозиторий Github.

Начнем с импорта данных:

import pandas as pd
url = "https://raw.githubusercontent.com/ozturkfemre/unsupervisedlearning/main/dataset/wdbc.data"
colnames = ["ID", "Diagnosis", "radius", "texture", "perimeter", "area", "smoothness", "compactness", "concavity", "concave.points", "symmetry", "fractal.dimension"]
df = pd.read_csv(url, header=None, usecols=[0,1,2,3,4,5,6,7,8,9,10,11])
df.columns = colnames

Поскольку я не использую первые два столбца, а именно ID и Diagnonsis, в кластерном анализе давайте их удалим.

df1 = df.copy() # it is always useful to create a copy of the data. 
df = df.drop(["ID", "Diagnosis"], axis = 1)

Описательная статистика

df.describe().T

Когда я проверяю описательную статистику набора данных:

  • Среднее значение переменной радиуса выше, чем медиана. Это означает, что переменная радиуса наклонена вправо. Когда мы сравниваем значения третьего квантиля и максимального значения, можно сказать, что в переменной могут быть некоторые выбросы. Тем не менее, лучше всего сделать окончательное суждение с помощью анализа блочной диаграммы.
  • Среднее значение переменной текстуры выше, чем медиана. Это означает, что переменная текстуры наклонена вправо. Когда мы сравниваем значения третьего квантиля и максимального значения, можно сказать, что в переменной могут быть некоторые выбросы. Тем не менее, лучше всего сделать окончательное суждение с помощью анализа блочной диаграммы.
  • Среднее значение переменной периметра выше, чем медиана. Это означает, что переменная периметра наклонена вправо. Когда мы сравниваем как значения третьего квантиля и максимальное значение, так и минимальное и значение первого квантиля, можно сказать, что в переменной могут быть некоторые выбросы. Тем не менее, лучше всего сделать окончательное суждение с помощью анализа блочной диаграммы.

Из описательной статистики легко можно сказать, что значения сильно различаются. Набор данных должен быть масштабирован. Однако, если существует высокая корреляция между парами переменных, к набору данных необходимо применить PCA. Таким образом, мне нужно проверить матрицу корреляции набора данных.

Корреляционный анализ

cm = df.corr()
cm

import seaborn as sns
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(15,10))
fig.patch.set_facecolor("#fbf9f4")
fig.set_facecolor("#fbf9f4")
sns.color_palette("mako", as_cmap=True)
sns.heatmap(cm, annot=True, annot_kws={"size": 12}, cmap="Blues_r", linewidths=2, linecolor='yellow')

Матрица корреляции набора данных показывает, что существует высокая корреляция между парами переменных. Корреляция между радиусом, периметром и площадью больше 0,98. Это слишком много. Корреляция между компактностью, вогнутостью и вогнутостью точек больше 0,83. Фрактальная размерность — единственная переменная с отрицательной корреляцией. Таким образом, PCA необходимо применять к набору данных.

Визуализация данных

fig = plt.figure(figsize = (25,15))

fig.add_subplot(2, 5, 1)
sns.boxplot(y=df['radius'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 2)
sns.boxplot(y=df['texture'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 3)
sns.boxplot(y=df['perimeter'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 4)
sns.boxplot(y=df['area'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 5)
sns.boxplot(y=df['smoothness'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 6)
sns.boxplot(y=df['compactness'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 7)
sns.boxplot(y=df['concavity'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 8)
sns.boxplot(y=df['concave.points'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 9)
sns.boxplot(y=df['symmetry'], color="#feb24c", linewidth=3, notch = True)

fig.add_subplot(2, 5, 10)
sns.boxplot(y=df['fractal.dimension'], color="#feb24c", linewidth=3, notch = True)

Из графиков видно, что в каждой переменной есть много выбросов. Дисперсия некоторых переменных (радиус, текстура, периметр, вогнутость, точки вогнутости) очень велика. На этом этапе может быть полезно отметить флажки переменных в соответствии с меткой столбца «Диагностика». Это помогло бы мне понять влияние переменных на классы.

fig = plt.figure(figsize = (25,15))

fig.add_subplot(2, 5, 1)
sns.boxplot(y=df1['radius'], x=df1['Diagnosis'],  linewidth=3, notch = True)

fig.add_subplot(2, 5, 2)
sns.boxplot(y=df1['texture'], x=df1['Diagnosis'], linewidth=3, notch = True)

fig.add_subplot(2, 5, 3)
sns.boxplot(y=df1['perimeter'], x=df1['Diagnosis'],  linewidth=3, notch = True)

fig.add_subplot(2, 5, 4)
sns.boxplot(y=df1['area'], x=df1['Diagnosis'],  linewidth=3, notch = True)

fig.add_subplot(2, 5, 5)
sns.boxplot(y=df1['smoothness'], x=df1['Diagnosis'],  linewidth=3, notch = True)

fig.add_subplot(2, 5, 6)
sns.boxplot(y=df1['compactness'], x=df1['Diagnosis'],  linewidth=3, notch = True)

fig.add_subplot(2, 5, 7)
sns.boxplot(y=df1['concavity'], x=df1['Diagnosis'],  linewidth=3, notch = True)

fig.add_subplot(2, 5, 8)
sns.boxplot(y=df1['concave.points'], x=df1['Diagnosis'], linewidth=3, notch = True)

fig.add_subplot(2, 5, 9)
sns.boxplot(y=df1['symmetry'], x=df1['Diagnosis'], linewidth=3, notch = True)

fig.add_subplot(2, 5, 10)
sns.boxplot(y=df1['fractal.dimension'], x=df1['Diagnosis'], linewidth=3, notch = True)

При рассмотрении диаграмм переменных в соответствии с уровнями диагностики можно заметить, что уровень M принимает более высокие значения почти для каждой переменной. Это справедливо не только для переменной fractal.dimension. Опять же, было замечено, что дисперсия уровня М была выше для всех переменных, кроме переменной fractal.dimension. Я считаю, что это делает набор данных кластеризуемым.

Однако также может быть полезно проверить статистику Хопкинса, которая сообщает нам, является ли набор данных кластеризуемым или нет.

from pyclustertend import hopkins
hopkins(pcadf, len(pcadf) - 1)
0.21065060484726908

Согласно статистике Хопкинса, набор данных можно кластеризовать. Это потому, что статистика Хопкинса близка к нулю.

Анализ основных компонентов

Так как я знаю из одного из предыдущих постов, что количество основных компонентов для этого набора данных равно 2, я продолжу с двумя основными компонентами.

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

sdf = StandardScaler().fit_transform(df)

pca = PCA(n_components=2)
principalComponents = pca.fit_transform(sdf)

pcadf = pd.DataFrame(data = principalComponents, columns = ['PC1', 'PC2'])

k-значит

Для теоретического объяснения k-средних вы можете прочитать этот пост.

Начнем с определения оптимального количества кластеров:

Метод локтя

from kneed import KneeLocator
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
Sum_of_squared_distances = []
K = range(1,10)
for num_clusters in K :
 kmeans = KMeans(n_clusters=num_clusters, n_init=25)
 kmeans.fit(pcadf)
 Sum_of_squared_distances.append(kmeans.inertia_)
plt.figure(figsize=(10,7))
plt.plot(K,Sum_of_squared_distances, 'x-')
plt.xlabel('cluster number') 
plt.ylabel('Total Within Cluster Sum of Squares') 
plt.title('Elbow Plot')
plt.show()

При анализе графа методом локтя можно сказать, что невозможно принять определенное решение о количестве кластеров, но можно выбрать два кластера.

Метод среднего силуэта

K = [2, 3, 4, 5, 6, 7, 8, 9, 10]
silhouette_avg = []
for num_clusters in K:
 
 # initialise kmeans
 km = KMeans(n_clusters=num_clusters, n_init=25)
 km.fit(pcadf)
 cluster_labels = km.labels_
 
 # silhouette score
 silhouette_avg.append(silhouette_score(pcadf, cluster_labels))

plt.figure(figsize=(10,7))
plt.plot(K,silhouette_avg,'bx-')
plt.xlabel('cluster number k') 
plt.ylabel('Silhouette score') 
plt.title('Average Silhouette Plot')
plt.show()

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

Дэвис — метод Булдина

from sklearn.metrics import davies_bouldin_score

K = [2, 3, 4, 5, 6, 7, 8, 9, 10]
db = []
for num_clusters in K:
 
 # initialise kmeans
 kmeans = KMeans(n_clusters=num_clusters, n_init=25)
 kmeans.fit(pcadf)
 cluster_labels = kmeans.fit_predict(pcadf)
 
 # silhouette score
 db.append(davies_bouldin_score(pcadf, cluster_labels))


plt.figure(figsize=(10,7))
plt.plot(K,db,'bx-')
plt.xlabel('cluster number k') 
plt.ylabel('Davies Bouldin score') 
plt.title('Davies Bouldin Plot')
plt.show()

При анализе графика Дэвиса-Булдина можно заметить, что самое низкое значение Дэвиса-Булдина приходится на 6 кластеров. Это совсем другое, чем другие. Я сгруппирую набор данных для 2 и 3 кластеров, как предлагает силуэт.

k-средних для k = 2

# clustering
kmeans2 = KMeans(n_clusters=2, random_state=0, n_init=25, algorithm='lloyd') 
kmeans2.fit(pcadf)

# output
zero = []
one = []
for i in kmeans2.labels_:
    if i == 0:
        zero.append(i)
    else:
        one.append(i)


print('\n',
      "Cluster centers:", '\n',
      "Cluster 0 :", kmeans2.cluster_centers_[0],'\n',
       "Cluster 1 :", kmeans2.cluster_centers_[1], '\n','\n',
        "Clustering vector:" ,'\n', kmeans2.labels_, '\n','\n',
         "Total Within Cluster Sum of Squares : ", '\n',
         kmeans2.inertia_ , '\n',
          "Observation numbers :", '\n',
          "Cluster 0 :", len(zero), '\n',
          "Cluster 1 :", len(one))
Cluster centers: 
 Cluster 0 : [ 3.00438761 -0.07488982] 
 Cluster 1 : [-1.29082985  0.03217628] 
 
 Clustering vector: 
 [0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1
 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0 1 1 0 0 1 1 1 1 0 1 1 0 1 1 1 1 0 1 0 1
 1 1 1 0 0 1 1 1 0 0 1 0 1 0 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 0 1 1 0 1 1
 1 0 1 1 1 1 0 0 1 1 0 0 1 1 1 1 0 0 0 1 0 0 1 0 1 1 1 0 1 1 1 1 1 1 1 0 1
 1 1 1 1 0 1 1 1 0 1 1 1 1 0 0 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 0 0 1 1 1
 1 1 1 1 1 0 1 1 1 0 1 0 0 0 1 1 0 0 0 1 1 1 1 1 1 0 1 0 0 0 1 1 1 0 0 1 1
 1 0 1 1 1 1 1 0 0 1 1 0 1 1 0 0 1 0 1 1 1 1 0 1 1 1 1 1 0 1 0 0 0 1 0 0 0
 0 0 1 0 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 1 1 0 0 0 1 1
 1 1 0 1 0 1 0 1 1 1 0 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0 0
 0 1 0 0 1 0 1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1
 1 0 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 1 0 1 0 0 1 1 1 1 1 1 1 0 1 1
 0 1 0 1 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1
 1 1 1 1 1 1 0 1 1 1 1 0 1 1 1 1 1 0 0 1 0 1 0 0 1 1 1 1 0 1 1 0 1 1 1 0 0
 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 0 0 0 0 1 0 1] 
 
 Total Within Cluster Sum of Squares :  
 2342.4243127486625 
 Observation numbers : 
 Cluster 0 : 171 
 Cluster 1 : 398

Когда исследуется результат кластеризации k-средних с 2 кластерами, обнаруживается следующее:

  • В кластере 1 398 наблюдений, в кластере 0 171 наблюдение.
  • Сумма квадратов внутри кластера равна 2342,4243127486657.

Давайте проверим, как выглядят кластеры:

from scipy.spatial import ConvexHull
from matplotlib.colors import to_rgba
sns.set_style("whitegrid")
data = pcadf
xcol = "PC1"
ycol = "PC2"
hues = [0,1]
colors = sns.color_palette("Paired", len(hues))
palette = {hue_val: color for hue_val, color in zip(hues, colors)}
plt.figure(figsize=(15,10))
g = sns.relplot(data=pcadf, x=xcol, y=ycol, hue=kmeans2.labels_, style=kmeans2.labels_, col=kmeans2.labels_, palette=palette, kind="scatter")
def overlay_cv_hull_dataframe(x, y, color, data, hue):
    for hue_val, group in pcadf.groupby(hue):
        hue_color = palette[hue_val]
        points = group[[x, y]].values
        hull = ConvexHull(points)
        plt.fill(points[hull.vertices, 0], points[hull.vertices, 1],
                 facecolor=to_rgba(hue_color, 0.2),
                 edgecolor=hue_color)
g.map_dataframe(overlay_cv_hull_dataframe, x=xcol, y=ycol, hue=kmeans2.labels_)
g.set_axis_labels(xcol, ycol)

plt.show()

Разделение можно наблюдать только в измерении PC1. В сумме квадратов кластера 1 больше, чем кластера 0. Причиной этого должна быть разница между числами наблюдений кластеров. Между кластерами нет видимого перекрытия.

Проверка кластера

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

Силуэт

from yellowbrick.cluster import silhouette_visualizer
plt.figure(figsize=(10,7))
silhouette_visualizer(kmeans2, pcadf, colors='yellowbrick')

При рассмотрении графика, содержащего значения силуэта каждого наблюдения, можно увидеть, что некоторые наблюдения в первом кластере, показанном синим цветом, имеют отрицательные значения. Это указывает на то, что эти наблюдения могли быть отнесены к неправильному кластеру.

silhouette_score(pcadf, kmeans2.labels_)
0.49228663332300016

Средняя оценка силуэта составляет 0,49. Это значение будет сравниваться с другими алгоритмами кластеризации.

Калински-Харабаш

from sklearn.metrics import calinski_harabasz_score
calinski_harabasz_score(pcadf, kmeans2.labels_)
534.4714168884335

Это значение будет сравниваться с другими алгоритмами кластеризации.

Скорректированный индекс Rand

from sklearn.metrics.cluster import adjusted_rand_score
adjusted_rand_score(df1['Diagnosis'],kmeans2.labels_)
0.6465880638205838

Это значение будет сравниваться с другими алгоритмами кластеризации.

Показатель точности

df1 = df1.assign(
    Diagnosis = lambda dataframe: dataframe["Diagnosis"].map(lambda Diagnosis: 0 if Diagnosis == "M" else 1)
)

from sklearn.metrics import accuracy_score
accuracy_score(df1['Diagnosis'],kmeans2.labels_)
0.9033391915641477

k-средних для k = 3

# clustering

kmeans3 = KMeans(n_clusters=3, random_state=0, n_init=25, algorithm='lloyd') 
kmeans3.fit(pcadf)

# output
zero = []
one = []
two = []
for i in kmeans3.labels_:
    if i == 0:
        zero.append(i)
    elif i == 1:
        one.append(i)
    else:
        two.append(i)


print('\n',
      "Cluster centers:", '\n',
      "Cluster 0 :", kmeans3.cluster_centers_[0],'\n',
       "Cluster 1 :", kmeans3.cluster_centers_[1],'\n',
        "Cluster 2 :", kmeans3.cluster_centers_[2], '\n',
        "Clustering vector:" ,'\n', kmeans3.labels_, '\n',
         "Total Within Cluster Sum of Squares : ", '\n',
         kmeans3.inertia_ , '\n',
          "Observation numbers :", '\n',
          "Cluster 0 :", len(zero), '\n',
          "Cluster 1 :", len(one), '\n',
          "Cluster 2 :", len(two))
Cluster centers: 
 Cluster 0 : [1.04646079 1.89652539] 
 Cluster 1 : [-1.53868518 -0.26518367] 
 Cluster 2 : [ 3.35917626 -1.13723881] 
 Clustering vector: 
 [0 2 2 0 2 0 2 0 0 0 1 0 2 1 0 0 1 0 2 1 0 1 0 2 2 0 0 2 0 2 2 0 2 2 0 2 0
 1 1 0 1 0 2 0 1 2 1 0 1 1 1 1 1 2 1 1 2 0 1 1 0 1 0 1 0 0 1 1 0 1 2 0 2 1
 1 1 0 2 2 1 1 0 2 2 1 2 1 2 1 0 1 1 1 1 0 2 1 1 1 0 1 1 1 1 1 0 1 1 2 1 1
 0 0 0 1 1 1 0 0 2 1 2 2 0 1 1 1 2 0 2 1 0 0 1 2 1 1 1 0 1 1 0 1 1 1 0 0 1
 1 1 0 0 0 1 1 1 2 1 1 1 0 2 2 1 2 1 1 1 2 1 1 1 0 1 1 1 0 2 1 1 2 2 1 1 1
 1 2 1 1 1 0 1 1 0 0 1 0 2 2 0 1 2 2 0 1 1 1 1 0 1 2 1 2 2 0 0 1 1 2 2 1 0
 1 0 1 1 1 1 1 0 2 1 1 2 1 1 2 2 1 2 1 1 0 1 2 1 1 1 1 1 2 1 2 2 2 0 2 0 0
 2 2 1 2 1 2 2 1 1 1 1 1 1 2 1 1 0 1 2 1 1 2 1 2 0 1 1 1 1 0 1 0 1 1 1 1 1
 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 1 0 2 1 2 1 1 1 1 0 0 0 1 1
 1 1 2 1 2 1 2 1 1 1 2 1 1 1 1 1 0 1 0 2 0 1 1 0 1 1 1 1 1 1 1 1 2 2 1 2 2
 2 1 2 2 1 0 0 1 1 0 0 1 1 1 1 1 1 1 1 2 1 1 0 2 1 1 1 1 1 1 2 1 1 1 1 1 1
 1 2 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 0 1 1 1 1 1 0 0 2 2 1 0 1 1 1 1 1 2 1 1
 2 1 2 1 1 2 1 2 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 2 0 1 1 1 1 1 1 1 1 1 0 1
 1 0 1 0 0 1 2 1 1 1 1 2 1 1 1 0 1 2 2 0 0 0 2 0 0 0 0 1 0 1 1 0 1 1 1 2 2
 0 0 0 2 1 1 1 1 1 1 0 1 1 1 1 2 1 2 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 0 2 2 2 1 2 1] 
 Total Within Cluster Sum of Squares :  
 1713.2725354315216 
 Observation numbers : 
 Cluster 0 : 117 
 Cluster 1 : 335 
 Cluster 2 : 117

Когда исследуется результат кластеризации k-средних с 3 кластерами, обнаруживается следующее:

  • В кластере 0 117 наблюдений, в кластере 2 117 наблюдений и в кластере 1 335 наблюдений.
  • Всего в пределах кластера сумма квадратов для кластеров составляет 1713,272535431522.
sns.set_style("whitegrid")
data = pcadf
xcol = "PC1"
ycol = "PC2"
hues = [0,1,2]
colors = sns.color_palette("Paired", len(hues))
palette = {hue_val: color for hue_val, color in zip(hues, colors)}
plt.figure(figsize=(15,10))
g = sns.relplot(data=pcadf, x=xcol, y=ycol, hue=kmeans3.labels_, style=kmeans3.labels_, col=kmeans3.labels_, palette=palette, kind="scatter")
def overlay_cv_hull_dataframe(x, y, color, data, hue):
    for hue_val, group in pcadf.groupby(hue):
        hue_color = palette[hue_val]
        points = group[[x, y]].values
        hull = ConvexHull(points)
        plt.fill(points[hull.vertices, 0], points[hull.vertices, 1],
                 facecolor=to_rgba(hue_color, 0.2),
                 edgecolor=hue_color)
g.map_dataframe(overlay_cv_hull_dataframe, x=xcol, y=ycol, hue=kmeans3.labels_)
g.set_axis_labels(xcol, ycol)

plt.show()

Перекрытия между кластерами нет.

Разделение можно наблюдать как в размерах ПК1, так и в размерах ПК2.

В сумме квадратов кластера 2 больше, чем других кластеров.

к-медоиды

Теоретическое объяснение k-medoids см. в этом посте.

Начнем с определения оптимального количества кластеров:

Метод локтя

Sum_of_squared_distances = []
K = range(1,10)
for num_clusters in K :
 kmedoid = KMedoids(n_clusters=num_clusters)
 kmedoid.fit(pcadf)
 Sum_of_squared_distances.append(kmedoid.inertia_)
plt.figure(figsize=(10,7))
plt.plot(K,Sum_of_squared_distances, 'x-')
plt.xlabel('cluster number') 
plt.ylabel('Total Within Cluster Sum of Squares') 
plt.title('Elbow Plot')
plt.show()

При анализе графа методом локтя можно сказать, что невозможно принять определенное решение о количестве кластеров, но можно выбрать два кластера.

Метод среднего силуэта

K = [2, 3, 4, 5, 6, 7, 8, 9, 10]
silhouette_avg = []
for num_clusters in K:
 
 # initialise kmeans
 kmedoids = KMedoids(n_clusters=num_clusters)
 kmedoids.fit(pcadf)
 cluster_labels = kmedoids.labels_
 
 # silhouette score
 silhouette_avg.append(silhouette_score(pcadf, cluster_labels))

plt.figure(figsize=(10,7))
plt.plot(K,silhouette_avg,'bx-')
plt.xlabel('cluster number k') 
plt.ylabel('Silhouette score') 
plt.title('Average Silhouette Plot')
plt.show()

При анализе графика силуэта можно заметить, что наибольшее значение силуэта приходится на два кластера.

Метод Дэвиса-Булдина

K = [2, 3, 4, 5, 6, 7, 8, 9, 10]
db = []
for num_clusters in K:
 
 # initialise kmeans
 kmedoids = KMedoids(n_clusters=num_clusters)
 kmedoids.fit(pcadf)
 cluster_labels = kmedoids.fit_predict(pcadf)
 
 # silhouette score
 db.append(davies_bouldin_score(pcadf, cluster_labels))


plt.figure(figsize=(10,7))
plt.plot(K,db,'bx-')
plt.xlabel('cluster number k') 
plt.ylabel('Davies Bouldin score') 
plt.title('Davies Bouldin Plot')
plt.show()

При анализе графика Дэвиса-Булдина можно заметить, что самое низкое значение Дэвиса-Булдина находится в двух кластерах.

k-медоиды для k = 2

# clustering
kmedoids2 = KMedoids(n_clusters=2)
kmedoids2.fit(pcadf)

# output
zero = []
one = []
for i in kmedoids2.labels_:
    if i == 0:
        zero.append(i)
    else:
        one.append(i)


print('\n',
      "Cluster medoids:", '\n',
      "Cluster 0 :", kmedoids2.cluster_centers_[0],'\n',
       "Cluster 1 :", kmedoids2.cluster_centers_[1], '\n','\n',
        "Clustering vector:" ,'\n', kmedoids2.labels_, '\n','\n',
         "Total Within Cluster Sum of Squares : ", '\n',
         kmedoids2.inertia_ , '\n',
          "Observation numbers :", '\n',
          "Cluster 0 :", len(zero), '\n',
          "Cluster 1 :", len(one))
Cluster medoids: 
 Cluster 0 : [ 2.35928485 -0.30157828] 
 Cluster 1 : [-1.35986794 -0.03765549] 
 
 Clustering vector: 
 [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1
 1 1 1 1 1 0 1 1 0 1 0 1 1 1 1 1 0 1 1 0 0 1 1 1 1 0 1 1 0 1 1 1 1 0 1 0 1
 1 0 1 0 0 1 1 0 0 0 1 0 1 0 1 0 1 0 1 1 0 0 1 1 1 1 1 1 1 1 1 0 1 1 0 1 1
 1 0 1 1 1 1 0 0 0 1 0 0 1 1 1 1 0 0 0 1 0 0 1 0 1 1 1 0 1 1 0 1 1 1 1 0 1
 1 1 1 1 0 1 1 1 0 1 1 1 1 0 0 1 0 1 1 0 0 1 1 1 0 1 1 1 1 0 1 1 0 0 1 1 1
 1 0 1 1 1 0 1 1 1 0 1 0 0 0 0 1 0 0 0 1 1 1 0 1 1 0 1 0 0 0 0 1 1 0 0 1 1
 1 0 1 1 1 1 1 0 0 1 1 0 1 1 0 0 1 0 1 1 1 1 0 1 1 1 1 1 0 1 0 0 0 1 0 0 0
 0 0 1 0 1 0 0 1 1 1 1 1 1 0 1 0 1 1 0 1 1 0 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1
 1 1 1 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 1 1 0 0 0 1 1
 1 1 0 1 0 1 0 1 1 1 0 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0 0
 0 1 0 0 1 0 1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1
 1 0 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 1 0 1 0 0 1 1 1 1 1 1 1 0 1 1
 0 1 0 1 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1
 1 1 1 0 1 1 0 1 1 1 1 0 1 1 1 1 1 0 0 1 0 1 0 0 1 1 1 1 0 1 1 0 1 1 1 0 0
 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 0 0 0 0 0 0 1] 
 
 Total Within Cluster Sum of Squares :  
 968.3783653743097 
 Observation numbers : 
 Cluster 0 : 190 
 Cluster 1 : 379

Когда результаты алгоритма кластеризации анализируются, можно констатировать следующее:

  • В кластере 0 190 наблюдений, в кластере 1 379 наблюдений, которые можно назвать несбалансированными.
  • Медоид кластера для кластера 0 равен [2,35928485 -0,30157828], [-1,35986794 -0,03765549] — для кластера 1.
  • Сумма квадратов внутри кластера равна 968,3783653743101.
sns.set_style("whitegrid")
data = pcadf
xcol = "PC1"
ycol = "PC2"
hues = [0,1]
colors = sns.color_palette("Paired", len(hues))
palette = {hue_val: color for hue_val, color in zip(hues, colors)}
plt.figure(figsize=(15,10))
g = sns.relplot(data=pcadf, x=xcol, y=ycol, hue=kmedoids2.labels_, style=kmedoids2.labels_, col=kmedoids2.labels_, palette=palette, kind="scatter")
def overlay_cv_hull_dataframe(x, y, color, data, hue):
    for hue_val, group in pcadf.groupby(hue):
        hue_color = palette[hue_val]
        points = group[[x, y]].values
        hull = ConvexHull(points)
        plt.fill(points[hull.vertices, 0], points[hull.vertices, 1],
                 facecolor=to_rgba(hue_color, 0.2),
                 edgecolor=hue_color)
g.map_dataframe(overlay_cv_hull_dataframe, x=xcol, y=ycol, hue=kmedoids2.labels_)
g.set_axis_labels(xcol, ycol)

plt.show()

При анализе двумерных и трехмерных графов перекрытия не наблюдается. Как и в k-средних, наблюдается, что разделение происходит только в измерении PC1. Дисперсия в кластере, показанном светло-синим цветом, выше.

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

Теоретическое объяснение иерархической кластеризации см. в этом посте.

В этом разделе я сгруппирую набор данных с помощью методов минимальной дисперсии Уорда и средней связи алгоритма иерархической кластеризации.

Метод минимальной дисперсии Уорда

import scipy.cluster.hierarchy as sch
plt.figure(figsize = (16 ,8))

dendrogram = sch.dendrogram(sch.linkage(pcadf, method  = "ward"))

plt.title("Dendrogram")
plt.show()

По дендограмме лучше всего резать расстояние на 35–40. Таким образом, 2 кластера подходят для этого метода.

# clustering
from sklearn.cluster import AgglomerativeClustering
hc = AgglomerativeClustering(n_clusters = 2, metric = "euclidean", linkage = "ward")
ward2 = hc.fit_predict(pcadf)

# output
zero = []
one = []
for i in ward2:
    if i == 0:
        zero.append(i)
    else:
        one.append(i)

print("Observation Numbers :", '\n',
    "Cluster 0: ", len(zero),'\n',
    "Cluster 1: ", len(one))
Observation Numbers : 
 Cluster 0:  180 
 Cluster 1:  389
sns.set_style("whitegrid")
data = pcadf
xcol = "PC1"
ycol = "PC2"
hues = [0,1]
colors = sns.color_palette("Paired", len(hues))
palette = {hue_val: color for hue_val, color in zip(hues, colors)}
plt.figure(figsize=(15,10))
g = sns.relplot(data=pcadf, x=xcol, y=ycol, hue=ward2, style=ward2, col=ward2, palette=palette, kind="scatter")
def overlay_cv_hull_dataframe(x, y, color, data, hue):
    for hue_val, group in pcadf.groupby(hue):
        hue_color = palette[hue_val]
        points = group[[x, y]].values
        hull = ConvexHull(points)
        plt.fill(points[hull.vertices, 0], points[hull.vertices, 1],
                 facecolor=to_rgba(hue_color, 0.2),
                 edgecolor=hue_color)
g.map_dataframe(overlay_cv_hull_dataframe, x=xcol, y=ycol, hue=ward2)
g.set_axis_labels(xcol, ycol)

plt.show()

Перекрытие наблюдается при анализе двумерного графика. Замечено, что разделение происходит только в измерении PC1. Дисперсия в кластере, показанном светло-синим цветом, выше.

Метод средней связи

plt.figure(figsize = (16 ,8))

dendrogram = sch.dendrogram(sch.linkage(pcadf, method  = "average"))

plt.title("Dendrogram")
plt.show()

Согласно дендограмме, в каждом случае будет дисбаланс между номерами наблюдений в кластерах. Тем не менее, похоже, что 3 кластера подходят для этого метода.

# clustering
hc = AgglomerativeClustering(n_clusters = 32, metric = "euclidean", linkage = "average")
average3 = hc.fit_predict(pcadf)

# output
zero = []
one = []
two = []
for i in average3:
    if i == 0:
        zero.append(i)
    elif i == 1:
        one.append(i)
    else:
        two.append(i)

print("Observation Numbers :", '\n',
    "Cluster 0: ", len(zero),'\n',
    "Cluster 1: ", len(one), '\n',
    "Cluster 2: ", len(two))
Observation Numbers : 
 Cluster 0:  30 
 Cluster 1:  24 
 Cluster 2:  515

Как видно из дендограммы, число наблюдений несбалансировано. Это не то, чего я хочу. Тем не менее, я проверю метрики проверки.

На этом пост закончился. Анализ будет продолжен с другими алгоритмами кластеризации.

Как всегда:

«На случай, если я тебя не увижу, добрый день, добрый вечер и спокойной ночи!»