Skip to content

クラスタについて

2021-05-23


クラスタ分析

クラスタ分析とは対象間の距離を定義して距離の近さによって対象を分類する統計手法の一つである。
簡単に言うと似たもの同士を見つけるための方法である。
クラスタ分析には階層的クラスタリングと非階層的クラスタリングの2種類がある。

クラスタについて

そもそもクラスタとは何かについてだが、クラスタとは集団、郡などの意味を持つ。
また、人間が任意の条件で分類したものはクラスタではなくグループと呼ぶ。例えば小学生の運動会の赤組、青組などはグループである。
クラスタは距離の近さによって分類する手法なので、似たもの同士で分類しているという感じである。分類する側に人間の意思が働けばグループ、そうでなければクラスタと考えていい。

階層的クラスタリング

階層的クラスタリングでは結果はデンドログラムとして扱われる。デンドログラムは完全クラスタリングと呼ばれ、完璧にクラスタリングされる。デンドログラムはスポーツなどのトーナメント表のような形で表現される。完全クラスタリングを実現する代わりに、データ量が多いと時間がかかってしまう。

階層的クラスタリングのアルゴリズムは最短距離法、最長距離法、群平均法、ウォード法がある。一般的に計算量は 𝑂(𝑁2log𝑁) とされている。 ただし、Lance-Williamsの更新式で定数時間で距離の更新が可能な上記の距離更新手法には可約性 (reducibility) という性質があり,再近隣グラフの性質を活用することで 𝑂(𝑁2) の時間で計算可能なアルゴリズムが知られている。

python でのデンドログラムの表現

python ではデンドログラムがとても簡単に表現することができる。
使用するライブラリは pandasnumpymatplotlibscipy である。

今回は乱数を使用してクラスタリングを行う。

import pandas as pd 
import numpy as np
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster
import matplotlib.pyplot as plt

df = pd.DataFrame(np.random.rand(16*21).reshape(21,16)) # 適当な値を格納する
Z = linkage(df, method='ward', metric='euclidean')
1
2
3
4
5
6
7

Z を出力すると以下のようになる。コメントアウトしてる部分が出力結果である。

print(Z)
# [[11.         16.          1.07569096  2.        ]
#  [10.         19.          1.20989437  2.        ]
#  [ 4.         15.          1.24983718  2.        ]
#  [ 6.         14.          1.27961914  2.        ]
#  [22.         23.          1.30262426  4.        ]
#  [ 8.         12.          1.30865321  2.        ]
#  [ 7.         13.          1.31970314  2.        ]
#  [ 1.          2.          1.3569622   2.        ]
#  [ 5.         18.          1.36003759  2.        ]
#  [ 0.         20.          1.38985074  2.        ]
#  [17.         21.          1.59515131  3.        ]
#  [28.         30.          1.62814148  4.        ]
#  [ 3.         26.          1.65895643  3.        ]
#  [ 9.         27.          1.75510187  3.        ]
#  [25.         29.          1.90373766  6.        ]
#  [31.         33.          2.01135602  6.        ]
#  [24.         35.          2.1351386   8.        ]
#  [32.         36.          2.16289886 10.        ]
#  [34.         38.          2.25559914 13.        ]
#  [37.         39.          2.49505445 21.        ]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

また、これを使用してデンドログラムを作成する。

dendrogram(Z)
plt.show()
1
2

たったこれだけである。
出力結果は以下のようになる。

dendrogram

これは下に行けば行くほど似ている、上に行けば行くほど似ていないことを表している。
非常に簡単にデンドログラムを表示することができた。

非階層的クラスタリング

非階層的クラスタリングは結果がデンドログラムではなくただ単にグルーピングがされた状態で表される。
非階層的クラスタリングでは k-means 法が有名である。

python での非階層的クラスタリング

あまりにも k-means 法が有名なので自力実装しようと思ったが授業の時にやってめんどくチェ〜ってなった記憶が蘇ってきたので自分用メモとして sklearn を使用して簡単に実装する。
全体像は以下のような感じ。インターネットの海に落ちていた卸売業のユーザーデータを使用してみた。これはどの記事を参照しても使われていたので多分 hello world 用なんだと思う、知らんけど。

import pandas as pd
import numpy as np
from sklearn.cluster import KMeans

# ネットに落ちてた卸売業の顧客データ
# Fresh, Milk, Grocery, Frozen, Detergents_Paper, Delicassen
cust_df = pd.read_csv('http://pythondatascience.plavox.info/wp-content/uploads/2016/05/Wholesale_customers_data.csv')
del(cust_df['Channel'])
del(cust_df['Region'])

print(cust_df)

#      Fresh   Milk  Grocery  Frozen  Detergents_Paper  Delicassen
# 0    12669   9656     7561     214              2674        1338
# 1     7057   9810     9568    1762              3293        1776
# 2     6353   8808     7684    2405              3516        7844
# 3    13265   1196     4221    6404               507        1788
# 4    22615   5410     7198    3915              1777        5185
# ..     ...    ...      ...     ...               ...         ...
# 435  29703  12051    16027   13135               182        2204
# 436  39228   1431      764    4510                93        2346
# 437  14531  15488    30243     437             14841        1867
# 438  10290   1981     2232    1038               168        2125
# 439   2787   1698     2510      65               477          52

# [440 rows x 6 columns]

# numpy で扱える形式に変換
cust_array = np.array(
    [
        cust_df['Fresh'].tolist(),
        cust_df['Milk'].tolist(),
        cust_df['Grocery'].tolist(),
        cust_df['Frozen'].tolist(),
        cust_df['Milk'].tolist(),
        cust_df['Detergents_Paper'].tolist(),
        cust_df['Delicassen'].tolist()
    ],
    np.int32
)

# 行列を転置
cust_array = cust_array.T

# クラスタ分析を実行(クラスタの数は4つ)
pred = KMeans(n_clusters=4).fit_predict(cust_array)

print(pred)
# 各顧客にクラスタ番号 { 0 || 1 || 2 || 3 } が付与されている
# [2 2 2 2 0 2 2 2 2 1 2 2 0 0 0 2 1 2 2 2 2 2 0 3 0 2 2 2 1 0 2 2 0 0 2 2 0
#  1 1 0 0 2 1 1 2 1 1 3 2 1 2 2 0 1 0 2 1 1 2 2 2 3 2 1 2 1 2 0 2 2 2 0 2 0
#  2 0 2 1 2 2 2 1 2 0 2 3 3 0 2 0 2 2 1 2 1 2 2 2 2 2 1 1 2 0 2 2 2 1 2 1 2
#  1 2 2 2 2 2 2 0 2 2 2 2 2 0 0 2 0 2 0 2 2 2 2 2 2 2 2 2 2 2 0 0 2 2 1 2 2
#  2 0 2 2 2 2 2 1 1 2 2 1 2 2 2 1 2 1 2 2 2 2 1 1 2 1 2 2 0 2 2 2 2 3 1 3 2
#  2 2 2 2 1 2 2 2 1 2 2 0 2 2 2 1 1 0 2 2 1 2 2 2 1 2 1 2 2 2 1 1 2 1 2 2 2
#  2 2 2 2 0 2 2 2 2 2 0 2 2 2 2 2 2 0 0 0 2 2 2 1 2 2 2 2 2 1 2 0 1 0 2 2 0
#  0 2 2 0 2 1 1 1 0 1 2 2 2 2 0 2 2 0 2 2 2 2 2 0 0 0 0 2 2 2 0 2 2 2 1 0 2
#  2 2 2 2 2 1 2 2 1 1 1 2 2 1 2 0 1 2 2 1 2 2 2 1 2 2 2 2 0 0 2 2 2 2 2 1 0
#  1 2 0 2 2 2 2 2 2 2 1 2 2 1 0 2 1 2 1 2 1 2 2 0 1 1 2 2 2 2 2 2 2 2 2 0 2
#  0 0 2 2 2 2 1 0 2 2 0 2 0 2 1 2 2 0 2 2 2 2 2 0 2 2 1 2 2 2 2 0 0 0 2 2 0
#  1 2 2 2 2 2 2 2 2 1 2 1 2 2 2 0 2 2 2 1 0 2 2 2 2 0 2 2 0 0 1 2 2]

# 確認する
cust_df['cluster_id'] = pred
print(cust_df)
#                                                                    ↓こいつが追加されている
#      Fresh   Milk  Grocery  Frozen  Detergents_Paper  Delicassen  cluster_id
# 0    12669   9656     7561     214              2674        1338           0
# 1     7057   9810     9568    1762              3293        1776           0
# 2     6353   8808     7684    2405              3516        7844           0
# 3    13265   1196     4221    6404               507        1788           0
# 4    22615   5410     7198    3915              1777        5185           2
# ..     ...    ...      ...     ...               ...         ...         ...
# 435  29703  12051    16027   13135               182        2204           2
# 436  39228   1431      764    4510                93        2346           2
# 437  14531  15488    30243     437             14841        1867           1
# 438  10290   1981     2232    1038               168        2125           0
# 439   2787   1698     2510      65               477          52           0

# [440 rows x 7 columns]

# id = 0 のクラスタ
print(cust_df[cust_df['cluster_id']==0].mean())
# Fresh               42117.285714
# Milk                46046.142857
# Grocery             42914.285714
# Frozen              10211.714286
# Detergents_Paper    17327.571429
# Delicassen          12192.142857
# cluster_id              0.000000
# dtype: float64

# matplotlib で可視化する
# 何を買っているかがわかりやすくなる
import matplotlib.pyplot as plt

clusterinfo = pd.DataFrame()
for i in range(4):
    clusterinfo['cluster{}'.format(str(i))] = cust_df[cust_df['cluster_id'] == i].mean()
clusterinfo = clusterinfo.drop('cluster_id')
 
my_plot = clusterinfo.T.plot(kind='bar', stacked=True, title="Mean Value of 4 Clusters")
my_plot.set_xticklabels(my_plot.xaxis.get_majorticklabels(), rotation=0)
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

コードの内容はコメントに書いたので省略する。
大体の方法は掴んだのでよかった。 グラフは以下のようになった。

k-means

画像からざっと読み取るあたり、

  • id=0 に分類されたユーザー (79 人) は、Grocery (食料雑貨品) と Detergents_Paper (衛生用品と紙類) が比較的高い
  • id=1 に分類された顧客 (291 人) は、全体的に購買額が低い傾向にある
  • id=2 に分類された顧客 (7 人) は、全てのジャンルで購買額が高い
  • id=3 に分類された顧客 (63 人) は、Fresh (生鮮食品) やFrozen (冷凍食品) の購買額が比較的高い

といった感じでしょうかね〜〜〜〜〜〜。グラフあった方がわかりやすい。