スタートページJavaScript他言語Python 目次>    ←判別分析

クラスター分析

キーワード:クラスター分析、k-means法、ウォード法、行列式の値、逆行列、固有値、固有ベクトル、連立方程式
sklearn.cluster, KMeans, scipy.cluster.hierarchy, linkage, dendrogram, fcluster

下記の青線の部分をGoogle Colaboratryの「コード」部分にコピーアンドペースト(ペーストは Cntl+V)して実行すれば、右図の画像が表示されます。


                
クラスター分析とは「似たもの集め」です。多くの手法がありますが、ここでは
  非階層型クラスタリングの「k-means法」
           from sklearn.cluster import KMeans
  階層型クラスタリングの「ウォード法」
           from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
を取り上げます。このような特定の分野では、専用のライブラリがあります

これらの機能を目立たたせるために、ここでは変数名を日本語にしています。

非階層型クラスタリング k-means

  from sklearn.cluster import KMeans
  KMeans(n_clusters = 指定クラス数)

下のDataFrameを3つのグループにクラス分けします。

import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
# ============  データ入力
df = pd.DataFrame([
                   [3, 4, 4, 5, 4, 4],
                   [6, 6, 7, 8, 7, 7],
                   [6, 5, 7, 5, 5, 6],
                   [6, 7, 5, 4, 6, 5],
                   [5, 7, 6, 5, 5, 5],
                   [4, 5, 5, 5, 6, 6],
                   [6, 6, 7, 6, 4, 4],
                   [5, 5, 4, 5, 5, 6],
                   [6, 6, 6, 7, 7, 6],
                   [6, 5, 6, 6, 5, 5],
                   [5, 4, 4, 5, 5, 5],
                   [5, 5, 6, 5, 4, 5],
                   [6, 6, 5, 5, 6, 5],
                   [5, 5, 4, 4, 5, 3],
                   [5, 6, 4, 5, 6, 6],
                   [6, 6, 6, 4, 4, 5],
                   [4, 4, 3, 6, 5, 6],
                   [6, 6, 7, 4, 5, 5],
                   [5, 3, 4, 3, 5, 4],
                   [4, 6, 6, 3, 5, 4]],
                 columns = ['c0','c1','c2','c3','c4','c5'])
指定クラス数 = 3         # 区分のクラス数

# ============ クラスタリング
計算方法 = KMeans(n_clusters = 指定クラス数) # 計算方法の指定
計算方法.fit(df)   # 計算方法を df に適用
                   #     この結果,計算方法.labels_に各行の帰属クラスのベクトルになる。
                   #     array([1, 2, 0, 0, 0, 1, …, 1, 0, 1, 0], dtype=int32)
df['class'] = 計算方法.labels_    # df の新列 class に帰属クラスを入れる
df.sort_values('class')           # class別にソートして一覧表表示
print(df.sort_values('class'))
# ============ クラスタリングとしてはこれで完了です。以下は散布図を描くため

# ============= プロット図にするには主成分分析により2軸にする必要があります。
# 主成分分析
from sklearn.decomposition import FactorAnalysis as FA
corr = df.corr()                     #  共分散行列
eig, eigv = np.linalg.eig(corr)      # eig 固有値
eign = len(eig[np.where(eig > 1)])   # 1より大の固有値の個数 2
fa = FA(n_components=eign, max_iter=500)
fa.fit(df)
df['f1'] = fa.fit_transform(df)[:,0]  # 第1因子の値
df['f2'] = fa.fit_transform(df)[:,1]  # 第2因子の値
# 散布図作成
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
df.loc[df['class'] == 0, 'color'] = 'r' # クラスの色
df.loc[df['class'] == 1, 'color'] = 'g'
df.loc[df['class'] == 2, 'color'] = 'b'
df.loc[df['class'] > 2, 'color'] = 'm' # 存在しないことはわかっているが
ax.scatter(df.f1, df.f2, color=df.color)
for i, txt in enumerate(df.index):                  # 各点の名称表示
    ax.annotate(txt, (df.f1[i], df.f2[i]), size=12) # 点の右上に表示されます。
ax.grid()
fig.show()

結果

       c0  c1  c2  c3  c4  c5  class
   9    6   5   6   6   5   5      0
  17    6   6   7   4   5   5      0
  15    6   6   6   4   4   5      0
  12    6   6   5   5   6   5      0
  11    5   5   6   5   4   5      0
   6    6   6   7   6   4   4      0
  19    4   6   6   3   5   4      0
   4    5   7   6   5   5   5      0
   3    6   7   5   4   6   5      0
   2    6   5   7   5   5   6      0
   7    5   5   4   5   5   6      1
  18    5   3   4   3   5   4      1
  10    5   4   4   5   5   5      1
  13    5   5   4   4   5   3      1
  14    5   6   4   5   6   6      1
  16    4   4   3   6   5   6      1
   5    4   5   5   5   6   6      1
   0    3   4   4   5   4   4      1
   8    6   6   6   7   7   6      2
   1    6   6   7   8   7   7      2

階層型クラスタリング ウォード法

ウオード(Ward)法×ユークリッド
 ・ユークリッド距離で最も近い点を一つのクラスにする。
 ・そのクラスの重心を基準として、重心が近い順にさらにクラスにまとめる
 ・クラス数が指定クラスになるまで繰り返す。
ライブラリ scipy にそのものズバリの機能があります。
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster

リンク表 = linkage(df, method='ward', metric='euclidean') # ウオード法×ユークリッドで計算せよ
  次の表が得られます。
       A   B    C    D
            [[ 7.  10.  1.414   2.]
             [15.  17.  1.414   2.]
             [ 3.  12.  1.414   2.]
             [ 9.  11.  1.732   2.]
              :
             [32.  33.  5.210   8.]
             [24.  35.  7.103  12.]
             [36.  37.  8.915  20.]]
   かなり加工をした後なので複雑です。下はイメージで不正確です。
   A:行番号
   B:同じクラスに属する行番号(複数あるので数字を増やししている)
   C:A・B間のユーグリッド距離
   D:グループ化されたデータの集合

dendrogram(df, ~) リンク表を利用して図表を作成
  ・各行が集まってクラスになり、クラスが更に大きいクラスにまとめている状況がわかります。
  ・指定閾値により打切りになり、各クラスに色を付けます。
  ・Y軸は横線につながっている2点のユーグリッド距離。短いほど近い関係にあります。

fcluster() リンク表を利用して、df の各行の帰属クラスを取得します。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster

# ============  データ入力
df = pd.DataFrame([
                   [3, 4, 4, 5, 4, 4],
                   [6, 6, 7, 8, 7, 7],
                   [6, 5, 7, 5, 5, 6],
                   [6, 7, 5, 4, 6, 5],
                   [5, 7, 6, 5, 5, 5],
                   [4, 5, 5, 5, 6, 6],
                   [6, 6, 7, 6, 4, 4],
                   [5, 5, 4, 5, 5, 6],
                   [6, 6, 6, 7, 7, 6],
                   [6, 5, 6, 6, 5, 5],
                   [5, 4, 4, 5, 5, 5],
                   [5, 5, 6, 5, 4, 5],
                   [6, 6, 5, 5, 6, 5],
                   [5, 5, 4, 4, 5, 3],
                   [5, 6, 4, 5, 6, 6],
                   [6, 6, 6, 4, 4, 5],
                   [4, 4, 3, 6, 5, 6],
                   [6, 6, 7, 4, 5, 5],
                   [5, 3, 4, 3, 5, 4],
                   [4, 6, 6, 3, 5, 4]],
                 columns = ['c0','c1','c2','c3','c4','c5'])

行番号 = df.index             # 行番号 0, 1, …, 19 
指定閾値 = 0.5                # 大にするとグループ区分数が小になる

# ============  計算
# 階層型クラスタリングの実施
# ウォード法 x ユークリッド距離
リンク表 = linkage(df, method='ward', metric='euclidean')
print('リンク表\n', リンク表)          #

閾値 = 指定閾値 * np.max(リンク表[:, 2])

# 図表(デンドログラム)の出力
dendrogram(リンク表, labels=行番号, color_threshold=閾値) 
plt.show()

# クラスタリング結果をdf['class']に入れる
帰属クラス = fcluster(リンク表, 閾値, criterion='distance')
                 # dfの各行におけるクラスからなるベクトルになります。
df['class'] = 帰属クラス
print('クラス分けした結果')
df.sort_values('class') # クラス順にソート

結果

リンク表
    ノード 距離(縦軸) 個数
   [[ 7.  10.   1.414    2.  ]
   [15.  17.   1.414    2.  ]
   [ 3.  12.   1.414    2.  ]
   [ 9.  11.   1.732    2.  ]
   [ 1.   8.   1.732    2.  ]
   [ 5.  14.   1.732    2.  ]
   [ 2.  23.   1.914    3.  ]
   [16.  20.   2.160    3.  ]
   [ 4.  22.   2.160    3.  ]
   [13.  18.   2.449    2.  ]
   [ 6.  26.   2.516    4.  ]
   [21.  30.   2.768    6.  ]
   [25.  27.   2.816    5.  ]
   [ 0.  29.   3.162    3.  ]
   [19.  28.   3.439    4.  ]
   [31.  34.   4.362   10.  ]
   [32.  33.   5.210    8.  ]
   [24.  35.   7.103   12.  ]
   [36.  37.   8.915   20.  ]]

クラス分けした結果
       c0   c1   c2   c3   c4   c5   class
  16    4    4    3    6    5    6     1  緑
   5    4    5    5    5    6    6     1
  14    5    6    4    5    6    6     1
   7    5    5    4    5    5    6     1
  10    5    4    4    5    5    5     1
   0    3    4    4    5    4    4     2  赤
  18    5    3    4    3    5    4     2
  13    5    5    4    4    5    3     2
   1    6    6    7    8    7    7     3  青
   8    6    6    6    7    7    6     3
  17    6    6    7    4    5    5     4  紫
  15    6    6    6    4    4    5     4
   9    6    5    6    6    5    5     4
  11    5    5    6    5    4    5     4
   6    6    6    7    6    4    4     4
   4    5    7    6    5    5    5     4
   3    6    7    5    4    6    5     4
   2    6    5    7    5    5    6     4
  12    6    6    5    5    6    5     4
  19    4    6    6    3    5    4     4