Python入門トップページ


k-means による非階層クラスタリングをしてみよう

サンプルデータ

下図のような2次元のサンプルデータについて \(k\)-means による非階層クラスタクラスタリングを実行してみよう.

cluster_sample

上の散布図を見ると,直感的には次の 3 つのクラスタに分類できそうである.

  • 右上のクラスタ
  • 左下のクラスタ
  • 右下のクラスタ

\(k\)-means は次のような手順でクラスタリングを行います.

  1. \(k\) 個のクラスタ(グループ)の中心となるデータをランダムに決める
  2. 各データを最も近いクラスタに割り当てる
  3. 各クラスタの中心を再計算する
  4. 収束するまで 2., 3. の処理を繰り返す

ここでは実際に非階層クラスタリングによって直感どおりの分類ができるかどうか確かめよう.

まず,Anaconda Prompt 等で pip list を実行して「scikit-learn」パッケージがインストールされていることを確認する.もしもインストールされていなければ pip install scikit-learnでインストールしておく.Jupyter Notebook のシェルコマンドを利用しても構いません.「scikit-learn」のインストールが確認できれば,必要なモジュールをインポートする.

モジュールのインポートimport pandas as pd
import numpy as np
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

次に,GitHub のリポジトリからサンプルデータ (clustering-sample.csv) を Pandas のデータフレームに読み込んで表示してみる.なお,Web ブラウザで CSV ファイルをダウンロードして Python プログラムと同じフォルダにコピーしたものを読み込んでも良い.

サンプルデータの読み込みurl = "https://github.com/rinsaka/sample-data-sets/blob/master/clustering-sample.csv?raw=true"
# url = "clustering-sample.csv"  # カレントディレクトリから読み込む場合
df =pd.read_csv(url)
df
clustering_sample_table-2021.png

上で表示した散布図と同じものを描いてみる.

散布図の作成fig, ax = plt.subplots(1, 1, figsize=(6, 6))
ax.scatter(df['x'], df['y'], alpha=0.5)
ax.set_title("Sample data")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 11)
# plt.savefig('cluster_sample.png', dpi=300, facecolor='white')
plt.show()
clustering_sample-2021.png

クラスタリング

Pandas のデータフレームから x 列と y 列を取り出して NumPy 配列に変換する.

Pandas のデータフレームから NumPy 配列に変換xy = df.loc[:, ['x', 'y']].values
print(xy)
[[ 7.4346  6.652 ]
 [ 6.5419  6.3611]
 [ 8.9819  9.2461]
 [ 3.8554  4.8386]

 ... (中略)

 [ 6.6627  7.3856]
 [ 5.7654  6.0543]
 [ 2.2411  1.0902]]

クラスタ数を \(k = 3\) に設定し,クラスタリングの計算を実行する.なお,この結果は毎回異なる可能性があるので,再現性を持たせたい場合は 2 行目をコメントアウトし,3 行目を有効にすると良い(詳細はこちら).

クラスタリングの計算を実行k = 3
clf = KMeans(n_clusters=k) # モデルの設定
# clf = KMeans(n_clusters=k, random_state=1) # 再現性を持たせたい場合
clf.fit(xy) # クラスタリングの計算
pred = clf.predict(xy) # 計算結果からサンプルデータがどのクラスタに属するかを予測する
pred
array([1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 0, 1, 2, 2, 1, 2, 0, 0, 2,
       0, 1, 1, 2, 0, 1, 2, 1, 2, 2, 1, 0, 2, 2, 1, 1, 2, 1, 0, 0, 2, 0,
       1, 1, 0, 1, 1, 0, 0, 1, 1, 2, 0, 0, 1, 0, 1, 2, 1, 0, 1, 1, 1, 1,
       1, 0, 0, 2, 0, 2, 2, 0, 2, 1, 2, 2, 1, 0, 0, 1, 0, 0, 2, 0, 0, 0,
       2, 0, 2, 0, 1, 0, 0, 2, 0, 1, 2, 1, 2, 0, 2, 2, 0, 0, 2, 1, 1, 2,
       0, 1, 0, 2, 1, 2, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 2, 0,
       1, 1, 0, 2, 2, 2, 1, 2, 0, 1, 2, 0, 2, 2, 2, 2, 1, 0, 2, 2, 2, 1,
       1, 2, 0, 0, 0, 2, 2, 1, 0, 0, 2, 2, 0, 1, 2, 2, 0, 0, 2, 1, 0, 0,
       2, 1, 0, 0, 2, 1, 1, 1, 2, 1, 2, 1, 0, 1, 1, 0, 0, 1, 2, 0, 2, 1,
       1, 1, 2, 1, 2, 1, 0, 0, 2, 0, 2, 0, 0, 0, 1, 1, 0, 2, 0, 0, 0, 0,
       1, 2, 0, 1, 0, 0, 0, 2, 1, 1, 1, 0, 0, 2, 2, 2, 1, 1, 2, 2, 2, 0,
       2, 0, 1, 1, 0, 1, 2, 0, 2, 1, 0, 0, 0, 0, 2, 2, 2, 1, 1, 1, 1, 2,
       1, 2, 0, 0, 0, 2, 1, 1, 2, 2, 2, 0, 1, 1, 1, 1, 2, 1, 0, 1, 1, 2,
       2, 2, 2, 2, 0, 1, 0, 2, 1, 2, 0, 1, 1, 2], dtype=int32)

Pandas のデータフレームに分類結果を追加する.

分類結果を追加するdf['cluster_id'] = pred
df
clustering_sample_result-2021.png

分類結果の散布図を描いてみる.直感どおりうまく分割されていることがわかる.

散布図の描画fig, ax = plt.subplots(1, 1, figsize=(6, 6))
colors = ['Red', 'Blue', 'Green']

for cls in range(k):
    x = df.loc[df['cluster_id'] == cls, 'x']
    y = df.loc[df['cluster_id'] == cls, 'y']
    ax.scatter(x, y, alpha=0.5, label=f"cluseter {cls}", color=colors[cls])

ax.set_title("Clustering results")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 11)
ax.legend(loc='upper left')
# plt.savefig('cluster_scatter.png', dpi=300, facecolor='white')
plt.show()
cluster_scatter-2021.png

各クラスタに属するサンプル数の分布を調査する.100 個ずつ 3 つのクラスタに分割されていることがわかる.

サンプル数の確認df['cluster_id'].value_counts()
0    100
1    100
2    100
Name: cluster_id, dtype: int64

各クラスタの特徴を確認する.このとき,ID の平均値 148.73 には意味がないことに注意する.クラスタ 0 は x が 8.21,y が 1.99 を中心とする付近のデータである.

クラスタ0df[df['cluster_id']==0].mean()
ID            148.730000
x               8.213697
y               1.991513
cluster_id      0.000000
dtype: float64

クラスタ 1 は x が 6.92,y が 7.85 を中心とする付近のデータである.

クラスタ1df[df['cluster_id']==1].mean()
ID            149.390000
x               6.919016
y               7.845478
cluster_id      1.000000
dtype: float64

クラスタ 2 は x が 2.92,y が 2.95 を中心とする付近のデータである.

クラスタ2df[df['cluster_id']==2].mean()
ID            150.380000
x               2.921705
y               2.946452
cluster_id      2.000000
dtype: float64

新たなデータが得られたときに,それらがどのクラスタに分類されるかテストしてみよう.

まずは,各クラスタの平均値に近いデータを与えてみる.

新たなデータで分析してみるt1 = np.array([[6., 8.], [9., 2.], [3., 3.]])
print(t1)
clf.predict(t1)
[[6. 8.]
 [9. 2.]
 [3. 3.]]
array([1, 0, 2], dtype=int32)

次に,各クラスタから離れた値やクラスタの境界に近いデータを与えてみる.

少々いやらしいデータではどのようなクラスタになるだろうか?t2 = np.array([[5., 11.], [11., 2.], [1., 6.], [5.8, 5.8], [5.8, 2.8], [5.0, 5.0]])
print(t2)
clf.predict(t2)
[[ 5.  11. ]
 [11.   2. ]
 [ 1.   6. ]
 [ 5.8  5.8]
 [ 5.8  2.8]
 [ 5.   5. ]]
array([1, 0, 2, 1, 0, 2], dtype=int32)