scikit-learn でクラスタ分析 (K-means 法)

本ページでは、Python の機械学習ライブラリの scikit-learn を用いてクラスタ分析を行う手順を紹介します。

クラスタ分析とは

クラスタ分析 (クラスタリング, Clustering) とは、ラベル付けがなされていないデータに対して、近しい属性を持つデータをグループ化する手法です。例をあげると、以下のような活用方法があり、マーケティング施策や商品の企画開発などに活用することます。

  • 製品ごとの特徴 (自動車であれば、価格や定員、燃費、排気量、直近の販売台数) を用いて類似の製品をグループ化
  • 店舗の特徴 (スーパーであれば、売上や面積、従業員数、来客数、駐車場の数) から類似の店舗をグループ化
  • 顧客の特徴 (銀行であれば、性別、年齢、貯蓄残高、毎月の支出、住宅ローンの利用有無など) を用いて似たような利用傾向の顧客をグループ化

クラスタ分析には大別して、K-Means に代表される「非階層的クラスタ分析」と Ward 法 (ウォード法) に代表される「階層的クラスタリング」の2種類が存在します。本ページでは、非階層的クラスタ分析の代表例である K-Means 法を用いたクラスタリングについて解説します。

非階層的クラスタリング
非階層的クラスタリング (例: K-Means 法) では、決められたクラスタ数にしたがって、近い属性のデータをグループ化します。以下の図では、3つのクラスタに分類しましたが、それぞれの色でどのクラスタに分類されたかを示しています。
kmeans

階層的クラスタリング
階層的クラスタリング (例: Ward 法) では、クラスタリングの結果を木構造で出力する特徴があります。縦方向の長さ (深さ) は類似度を示し、長いほど類似度が低く、短いほど類似度が高いことを示します。
ward


K-Means法とは

K-Means 法 (K-平均法ともいいます) は、基本的には、以下の 3 つの手順でクラスタリングを行います。

  1. 初期値となる重心点をサンプルデータ (データセット全体からランダムに集めた少量のデータ) から決定。
  2. 各サンプルから最も近い距離にある重心点を計算によって求め、クラスタを構成。
  3. 2.で求めたクラスタごとに重心を求め、2. を再度実行する。2. ~ 3. を決められた回数繰り返し実行し、大きな変化がなくなるまで計算。

scikit-learn を用いたクラスタ分析

scikit-learn には、K-means 法によるクラスタ分析を行うクラスとして、sklearn.cluster.KMeans クラスが用意されています。

sklearn.cluster.KMeans クラスの使い方

sklearn.cluster.KMeans(n_clusters=8, init='k-means++', n_init=10, max_iter=300,
                       tol=0.0001,precompute_distances='auto', verbose=0,
                       random_state=None, copy_x=True, n_jobs=1)

sklearn.cluster.KMeans クラスの引数
実行時に、以下のパラメータを制御できます。

n_clusters クラスタ数。(デフォルト値: 8)
max_iter 繰り返し回数の最大値。 (デフォルト値: 300)
n_init 初期値選択において、異なる乱数のシードで初期の重心を選ぶ処理の実行回数。 (デフォルト値: 10)
init 初期化の方法。’k-means++”, ‘random’ もしくは ndarray を指定。 (デフォルト値: ‘k-means++’)
tol 収束判定に用いる許容可能誤差。 (デフォルト値: 0.0001)
precompute_distances 距離 (データのばらつき具合) を事前に計算するか。 ‘auto’, True, False から指定。 (デフォルト値: ‘auto’)
verbose 1 を指定すると、詳細な分析結果を表示。 (デフォルト値: 0)
random_state 乱数のシードを固定する場合に指定。数値もしくは integer or numpy.RandomState で指定。 (デフォルト値: None)
copy_x 距離を事前に計算する場合、メモリ内でデータを複製してから実行するかどうか。 (デフォルト値: True)
n_jobs 初期化を並列処理する場合の多重度。-1 を指定するとすべての CPU を使用。 (デフォルト値: 1)

sklearn.cluster.KMeans クラスのメソッド
以下のメソッドを用いて、クラスタリングの計算を行います。

fit(X[, y]) クラスタリングの計算を実行する。
fit_predict(X[, y]) 各サンプルに対する、クラスタ番号を求める。
fit_transform(X[, y]) クラスタリングの計算を行い、X を分析に用いた距離空間に変換して返す。
get_params([deep]) 計算に用いたパラメータを返す。
predict(X) X のサンプルが属しているクラスタ番号を返す
set_params(**params) パラメータを設定する
transform(X[, y]) X を分析に用いた距離空間に変換して返す。

scikit-learn を用いたクラスタ分析の実行例

scikit-learn を用いてクラスタ分析を行う手順を紹介します。

今回使用するデータ
今回は、UC バークレー大学の UCI Machine Leaning Repository にて公開されている、「Wholesale customers Data Set (卸売業者の顧客データ)」を利用します。

データセットの構成は以下のようになっています。各行が顧客 1 件を指し、440 件の顧客データが格納されています。

Channel 販売チャネル。1: Horeca (ホテル・レストラン・カフェ), 2: 個人向け小売
Region 各顧客の地域。1: リスボン市, 2: ポルト市, 3: その他
Fresh 生鮮品の年間注文額
Milk 生鮮品の年間注文額
Grocery 食料雑貨の年間注文額
Frozen 冷凍食品の年間注文額
Detergents_Paper 衛生用品と紙類の年間注文額
Delicassen 惣菜の年間注文額

データセットの詳細は以下にて確認可能です。

分析用コード

以下のコードを実行して、クラスタ分析を実行できます。今回は、440 件の顧客を購買傾向に基づいて、4 つのクラスタに分類します。
(※ K-Means 法は初期値に乱数を使用する関係上、必ずしも以下の結果通りにクラスタ番号が決定するとは限りません)

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

>>> # データセットを読み込み
>>> cust_df = pd.read_csv("https://pythondatascience.plavox.info/wp-content/uploads/2016/05/Wholesale_customers_data.csv")

>>> # 不要なカラムを削除
>>> del(cust_df['Channel'])
>>> del(cust_df['Region'])
>>> 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
5     9413   8259     5126     666              1795        1451
6    12126   3199     6975     480              3140         545
7     7579   4956     9426    1669              3321        2566
8     5963   3648     6192     425              1716         750
9     6006  11093    18881    1159              7425        2098
10    3366   5403    12974    4400              5977        1744
 ... (中略)
439   2787   1698     2510      65               477          52

[440 rows x 6 columns]

>>> # Pandas のデータフレームから Numpy の行列 (Array) に変換
>>> 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)
>>> pred
array([1, 1, 1, 1, 3, 1, 1, 1, 1, 0, 1, 1, 3, 3, 3, 1, 0, 1, 1, 1, 1, 1, 3,
       2, 3, 1, 1, 1, 0, 3, 1, 1, 1, 3, 1, 1, 3, 0, 0, 3, 3, 1, 0, 0, 1, 0,
       0, 2, 1, 0, 1, 1, 3, 0, 3, 1, 0, 0, 1, 1, 1, 2, 1, 0, 1, 0, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 2, 2, 3, 1, 3, 1, 1,
       0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 3, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 3, 3, 1, 1, 0, 1, 1, 1, 3, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1,
       1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 3, 1, 1, 1, 1, 2, 0, 2,
       1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 3, 1, 1, 1, 0, 0, 3, 1, 1, 0, 1,
       1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1,
       3, 0, 3, 1, 1, 3, 3, 1, 1, 3, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 3, 1, 1,
       3, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 3, 1, 1, 1, 0, 1, 1, 1, 1, 1,
       1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 3, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1,
       1, 1, 3, 3, 1, 1, 1, 1, 1, 0, 3, 0, 1, 3, 1, 1, 1, 1, 1, 1, 1, 0, 1,
       1, 0, 3, 1, 0, 1, 0, 1, 0, 1, 1, 3, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       3, 1, 3, 1, 1, 1, 1, 1, 0, 3, 1, 1, 3, 1, 3, 1, 0, 1, 1, 1, 1, 1, 1,
       1, 1, 3, 1, 1, 0, 1, 1, 1, 1, 3, 3, 3, 1, 1, 3, 0, 1, 1, 1, 1, 1, 1,
       1, 1, 0, 1, 0, 1, 1, 1, 3, 1, 1, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 3, 3,
       0, 1, 1])

上記のように、440 件の各顧客にクラスタ番号 (0, 1, 2, 3) が付与されたことがわかります。


各クラスタの特徴を確認

クラスタ分析の結果を利用し、各クラスタがどのような特徴があるのかを確認します。ここでは、集計作業を楽に行うため、Pandas のデータフレームを利用します。

>>> # Pandas のデータフレームにクラスタ番号を追加
>>> cust_df['cluster_id']=pred
>>> cust_df
     Fresh   Milk  Grocery  Frozen  Detergents_Paper  Delicassen  cluster_id
0    12669   9656     7561     214              2674        1338           1
1     7057   9810     9568    1762              3293        1776           1
2     6353   8808     7684    2405              3516        7844           1
3    13265   1196     4221    6404               507        1788           1
4    22615   5410     7198    3915              1777        5185           3
5     9413   8259     5126     666              1795        1451           1
6    12126   3199     6975     480              3140         545           1
7     7579   4956     9426    1669              3321        2566           1
8     5963   3648     6192     425              1716         750           1
9     6006  11093    18881    1159              7425        2098           0
10    3366   5403    12974    4400              5977        1744           1
 ... (中略)
439   2787   1698     2510      65               477          52           1

[440 rows x 7 columns]

>>> # 各クラスタに属するサンプル数の分布
>>> cust_df['cluster_id'].value_counts()
1    291
0     79
3     63
2      7
Name: cluster_id, dtype: int64

>>> # 各クラスタの各部門商品の購買額の平均値
>>> cust_df[cust_df['cluster_id']==0].mean() # クラスタ番号 = 0
Fresh                4899.607595
Milk                12689.063291
Grocery             19743.240506
Frozen               1653.481013
Detergents_Paper     8947.278481
Delicassen           1718.000000
cluster_id              0.000000
dtype: float64

>>> cust_df[cust_df['cluster_id']==1].mean() # クラスタ番号 = 1
Fresh               8524.848797
Milk                3156.395189
Grocery             4363.261168
Frozen              2709.958763
Detergents_Paper    1282.319588
Delicassen          1087.085911
cluster_id             1.000000
dtype: float64

>>> cust_df[cust_df['cluster_id']==2].mean() # クラスタ番号 = 2
Fresh               42117.285714
Milk                46046.142857
Grocery             42914.285714
Frozen              10211.714286
Detergents_Paper    17327.571429
Delicassen          12192.142857
cluster_id              2.000000
dtype: float64

>>> cust_df[cust_df['cluster_id']==3].mean() # クラスタ番号 = 3
Fresh               33611.269841
Milk                 4874.396825
Grocery              5852.968254
Frozen               5729.285714
Detergents_Paper     1056.730159
Delicassen           2119.587302
cluster_id              3.000000
dtype: float64

Matplotlib でクラスタの傾向を可視化

先ほど求めた、各クラスタの各部門商品の購買額の平均値を Matplotlib を用いて傾向を可視化すると以下のようになります。

Matplotlib で積み上げ棒グラフを出力

# 可視化(積み上げ棒グラフ)
import matplotlib.pyplot as plt

clusterinfo = pd.DataFrame()
for i in range(4):
    clusterinfo['cluster' + 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)

出力結果

k-means_plot

結果から、それぞれ次のように説明できます。

  • クラスタ番号 = 0 に分類された顧客 (79 人) は、Grocery (食料雑貨品) と Detergents_Paper (衛生用品と紙類) の購買額が比較的高いことがわかります。
  • クラスタ番号 = 1 に分類された顧客 (291 人) は、全体的に購買額が低い傾向にあります。
  • クラスタ番号 = 2 に分類された顧客 (7 人) は、全てのジャンルで購買額が高いと言えます。
  • クラスタ番号 = 3 に分類された顧客 (63 人) は、Fresh (生鮮食品) やFrozen (冷凍食品) の購買額が比較的高いことがわかります。

上記のように、クラスタ分析は簡単にデータのみからあらゆる発見を行うことに適している汎用的な手法だと言えます。皆さんが会社や研究で扱っているデータもこのように分析することで、新たな発見があるかもしれないでしょう。

参考・引用:
2.3. Clustering — scikit-learn 0.17.1 documentation
sklearn.cluster.KMeans — scikit-learn 0.17.1 documentation