Python入門トップページ


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

目次

  1. サンプルデータとクラスタリング
  2. サンプルデータ1
    1. 散布図の作成
    2. クラスタリング
  3. サンプルデータ2
    1. 散布図の作成
    2. クラスタリング
    3. クラスタリングの手順を感覚的に理解する

目次に戻る

サンプルデータとクラスタリング

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

kmeans-2024-1-01
kmeans-2024-2-01

上の散布図を見ると,どちらも直感的には3つのクラスタに分類できそうです.\(k\)-means は次のような手順でクラスタリングを行います.

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

ここでは実際に非階層クラスタリングによって直感どおりの分類ができるかどうか確かめます.さらに,サンプルデータ2について,クラスタリングが実際にどのような手順で行われるかについて感覚的に理解します.

目次に戻る

サンプルデータ1

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

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

from IPython.display import set_matplotlib_formats
# from matplotlib_inline.backend_inline import set_matplotlib_formats # バージョンによってはこちらを有効に
set_matplotlib_formats('retina')

次に,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 1")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 11)
# plt.savefig('cluster_sample1.png', dpi=300, facecolor='white')
plt.show()
kmeans-2024-1-01

目次に戻る

クラスタリング

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 1")
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_scatter1.png', dpi=300, facecolor='white')
plt.show()
kmeans-2024-1-02

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

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

各クラスタの特徴を確認します.このとき,ID の平均値 148.73 には意味がないことに注意が必要です.

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

IDの平均値を表示せず,x と y の平均値だけを表示するように変更します.クラスタ 0 の中心は x が 8.21,y が 1.99 です.

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

クラスタ 1 の中心は x が 6.92,y が 7.85 です.

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

クラスタ 2 の中心は x が 2.92,y が 2.95 です.

クラスタ2
df[df['cluster_id']==2][['x','y']].mean()
x    2.921705
y    2.946452
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)

目次に戻る

サンプルデータ2

次に,12個の2次元データからなる2つ目のサンプルデータについてクラスタリングを行います.上と同様にまず,必要なモジュールをインポートします.

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

from IPython.display import set_matplotlib_formats
# from matplotlib_inline.backend_inline import set_matplotlib_formats # バージョンによってはこちらを有効に
set_matplotlib_formats('retina')

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

サンプルデータの読み込み
url = "https://github.com/rinsaka/sample-data-sets/blob/master/clustering-sample-kmeans.csv?raw=true"
# url = "clustering-sample-kmeans.csv"  # カレントディレクトリから読み込む場合
df =pd.read_csv(url)
print(df)
   ID  x  y
0   A  2  9
1   B  8  9
2   C  1  7
3   D  4  2
4   E  7  8
5   F  5  4
6   G  6  3
7   H  6  2
8   I  8  7
9   J  3  8
10  K  6  7
11  L  2  6

目次に戻る

散布図の作成

サンプルデータ2についても散布図を作成します.

散布図の作成
fig, ax = plt.subplots(1, 1, figsize=(6, 6))
ax.scatter(df['x'], df['y'], alpha=0.8)
ax.set_title("Sample data 2")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
# plt.savefig('kmeans-2-01.png', dpi=300, facecolor='white')
plt.show()
kmeans-2024-2-01

目次に戻る

クラスタリング

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

Pandas のデータフレームから NumPy 配列に変換
xy = df.loc[:, ['x', 'y']].values
print(xy)
[[2 9]
 [8 9]
 [1 7]
 [4 2]
 [7 8]
 [5 4]
 [6 3]
 [6 2]
 [8 7]
 [3 8]
 [6 7]
 [2 6]]

クラスタ数を \(k = 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, 2, 1, 0, 2, 0, 0, 0, 2, 1, 2, 1], dtype=int32)

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

分類結果を追加する
df['cluster_id'] = pred
print(df)
   ID  x  y  cluster_id
0   A  2  9           1
1   B  8  9           2
2   C  1  7           1
3   D  4  2           0
4   E  7  8           2
5   F  5  4           0
6   G  6  3           0
7   H  6  2           0
8   I  8  7           2
9   J  3  8           1
10  K  6  7           2
11  L  2  6           1

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

散布図の描画
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.8, label=f"cluseter {cls}", color=colors[cls])

ax.set_title("Clustering results 2")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.legend(loc='lower right')
# plt.savefig('kmeans-2-02.png', dpi=300, facecolor='white')
plt.show()
kmeans-2024-2-02

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

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

クラスタ 0 の中心は x が 5.25,y が 2.75 です.

クラスタ0
df[df['cluster_id']==0][['x','y']].mean()
x    5.25
y    2.75
dtype: float64

クラスタ 1 の中心は x が 2.0 が 7.5 です.

クラスタ1
df[df['cluster_id']==1][['x','y']].mean()
x    2.0
y    7.5
dtype: float64

クラスタ 2 の中心は x が 7.25,y が 7.75 です.

クラスタ2
df[df['cluster_id']==2][['x','y']].mean()
x    7.25
y    7.75
dtype: float64

目次に戻る

クラスタリングの手順を感覚的に理解する

ここでは,クラスタリングがどのような手順で行われているかについて感覚的に理解します.まず,散布図にデータラベルを追加してみます.

散布図にデータラベルを追加
fig, ax = plt.subplots(1, 1, figsize=(6, 6))
ax.scatter(df['x'], df['y'], alpha=0.8)
ax.set_title("Sample data 2")
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)

annotations = df['ID'].values
for i, label in enumerate(annotations):
    plt.annotate(label, (df['x'][i], df['y'][i]))

# plt.savefig('kmeans-2-03.png', dpi=300, facecolor='white')
plt.show()
kmeans-2024-2-03

クラスタリングの手順を再び確認します.

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

上の手順がどのように実行されるかを感覚的に理解するための例を示します.

Step 1: クラスタ数は \(k=3\) なので,3個のクラスタの中心となるデータをランダムに決めます.例えば,A, B, C という3つのデータが中心として選ばれたとします.

kmeans-2024-2-04

Step 2: 各データについて,3つの中心までの距離を求め,最も近い中心のクラスタに割り当てます. 「J」のデータは中心「A」が最も近く,「E, G, K, I」のデータは中心「B」が最も近く,「D, F, H, L」は中心「C」が最も近いことがわかったので,それぞれのクラスタに割り当てます.

kmeans-2024-2-05

Step 3: 各クラスタの中心を再計算します.具体的には「A, J」から成るクラスタ0についてその中心となる座標を求めこれを「Z0」とします.同様に,クラスタ1とクラスタ2についても中心を求めそれぞれ「Z1」,「Z2」とします.これによって中心の位置が移動しました.

kmeans-2024-2-06

Step 2: 各データについて,3つの中心までの距離を求め,最も近い中心のクラスタに割り当てます.

kmeans-2024-2-07

その結果,次のような3つのクラスタになりました.

kmeans-2024-2-08

Step 3: 各クラスタの中心を再計算します.

kmeans-2024-2-09

この時点で Step 2 に戻ってもクラスタの割り当てが変化することなく,その結果,Step 3 で計算する中心も動かなくなり,結果が収束したことになります.よって計算が終了しました.各クラスタの中心はここで求めた値と一致しているいことも確認してください.(ただし,クラスタ番号は異なる可能性があることにも注意してください.クラスタ番号は Step 1 でどのデータが選択されるか,またどの番号が割り当てられるか,によって変化することも合わせて理解するとよいでしょう.)

目次に戻る