クラスター分析

所要時間

300分

学ぶコト

・クラスター分析の概要
・階層的クラスター分析をPythonで実装
・非階層的クラスター分析をPythonで実装

階層的クラスター分析の実装に挑戦!

それでは、クラスター分析の実装をおこなっていきましょう!

まずは階層的クラスター分析から見ていきます。

ここでも今までと同様にirisデータを使っていきます。

まずはライブラリのインポートをしていきましょう!

# ライブラリの読み込み
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster

例によってseabornを使ってirisのデータをインポートし、目的変数のspeciesは除外します。

# irisデータの読み込み
df = sns.load_dataset("iris")
df_x = df.drop('species', axis=1)

そしてここからがクラスター分析の実施です。

Z = linkage(df_x)
Z

Zを出力すると多くの数値が出力されたかと思いますが、ここではクラスター間の距離を算出しています。

そしてこの距離に基づいてクラスタ分けをおこなっていきます。

plt.figure(figsize=(20,10))
dendrogram(Z)
plt.show()
階層的クラスター分析

このようにトーナメント表のような階層構造が表示されたかと思います。

このような図をデンドログラムと言います。

これこそ階層的クラスター分析と呼ばれる所以であり、特定の距離基準に基づいてどんどんクラスタが形成されていくのがこのクラスター分析です。

ただ、このデンドログラムを見ると、かなり鎖状の特殊な状態になっていることが分かります。

トーナメントで考えると全員が1回戦からではなくて、ありえないシードが存在するような状態になってしまっています。

これは実はクラスター間の距離計算をする際の基準がデフォルトになっているためです。

クラスター分析では、クラスター間の距離を特定の手法を用いて計算しそれが最小になるようにクラスターを分けていきます。

そのため、この「手法」によって結果が変わってきてしまうのです。

特にZ = linkage(df_x)でデフォルトで指定されているsingleは最短距離法といい、クラスター間のサンプルのうち最短のサンプルの距離をそのクラスターの距離とします。

2つのクラスターがあったとしたら以下のようなイメージです。

階層的クラスター分析

この最短距離法だと鎖効果が出やすくなり先ほどのデンドログラムのような状態になります。

それを防ぐためには他の手法を用いることをオススメします。

一般的にもよく使われる群平均法を使ってみましょう。

群平均法は各クラスター間のそれぞれの要素の距離の平均を取ったものをクラスター間の距離とする手法です。

階層的クラスター分析

以下のようにmethodにaverageを指定してあげるだけで大丈夫です。

Z = linkage(df_x, method="average")
plt.figure(figsize=(20,10))
dendrogram(Z)
plt.show()
階層的クラスター分析

先ほどよりはだいぶよくなったかと思います。

それではこのデンドログラムを見ながら閾値を決めてクラスタに分けていきましょう!

デンドログラムを見ると、1.5あたりで4つのクラスタに分かれることが分かるので閾値を1.5にしてクラスタ分けをしてみましょう。

threshold = 1.5
clustered = fcluster(Z, threshold, criterion='distance')
clustered

array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 4, 3, 4, 2, 4, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 4, 2, 4, 2, 4, 2, 2, 4, 4, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 4, 2, 2, 2, 4, 2, 2, 2, 4, 2, 2, 4], dtype=int32)

thresholdは閾値を表し、今回クラスタに切るのに使う1.5という基準を代入しています。

これにより複数のクラスターに分かれましたね!

それではこれらのクラスターを元データに結合して結果を見ていきましょう。

以下のように記述してあげることで、元のデータに対して各クラスターの情報がひも付きます。

df = pd.concat([df, pd.DataFrame(clustered).rename(columns={0:"cluster"})], axis=1)

concatを使うことでデータフレーム同士を結合させることができます。

ここではclusteredを一旦データフレーム化して、renameで列名をclusterに変更しています。

ちなみにconcatを使う時にaxis=1にしないと列方向の結合にならず行方向の結合をしてしまうので注意しましょう!

これでクラスターが元データに紐づきました。

それでは、それぞれの[“setosa”, “virginica”, “versicolor”]がどのようなクラスターに分かれているか見ていきましょう!

どうやったらそれぞれの種類のクラスター分布があるか見ることができるでしょうか?

ちょっと考えてみましょう!

課題

[“setosa”, “virginica”, “versicolor”]それぞれに対して4つのクラスターがどのように分かれているか集計する

解答を見る
print("setosa")
print(df[df["species"] == "setosa"]["cluster"].value_counts())
print("virginica")
print(df[df["species"] == "virginica"]["cluster"].value_counts())
print("versicolor")
print(df[df["species"] == "versicolor"]["cluster"].value_counts())

setosa
1 50
Name: cluster, dtype: int64
virginica
2 36
4 14
Name: cluster, dtype: int64
versicolor
4 46
3 4
Name: cluster, dtype: int64

これを見ると、setosaは上手く1つのクラスターにおさまっているのですが、やはりvirginicaとversicolorの分類が困難であることが分かります。

今回はあえて4つのクラスターに分かれるように指定しました。

クラスターを変えると分かれ方も変わるので是非試してみましょう!

ちなみに今回は[“setosa”, “virginica”, “versicolor”]という3種類の正解データがありましたが、通常クラスター分析を扱う際は正解データがありません。

そのためデンドログラムを見ながら適切なクラスターを見定めていくことになるのです。

非階層的クラスター分析の実装に挑戦!

さて、動画の中では「実務では非階層的クラスター分析を使うことが多い」と学びました。

階層的クラスター分析は可視化に優れており、デンドログラムを見ながら当たりをつけてクラスター数を決めることができるのですが、大量のデータに対しては「膨大な計算負荷がかかってしまう」かつ「デンドログラムが見にくくなってしまう」のです。

そこでしばしば用いられるのが高速で実装できるk-means法

それではk-means法を実際に使っていきましょう!

例によってまずはライブラリをインポートしていきましょう!

import numpy as np
import pandas as pd
from sklearn.datasets import fetch_california_housing
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

ここではirisデータではなく「線形回帰」のコースでも登場したカリフォルニアの住宅価格データを扱っていきます。

まずは、以下のようにカリフォルニアの住宅価格データをインポートして出力してみましょう!

# データ読み込み
california = fetch_california_housing()

# 特徴量データ(8カラム)
df = pd.DataFrame(california.data, columns=california.feature_names)
display(df.head())
クラスター分析

住宅価格データが表示されたと思います。

目的変数はcalifornia.targetに入っているのですが、今回はクラスター分析なので目的変数は使わず説明変数だけで実装していきます。

説明変数が何を表しているかに関しては以下のように記述することで確認することができます。

print(california["DESCR"],"\n")

日本語に直すと以下のようなカラムになります。

Columns:
1) MedInc : 地域の平均所得
2) HouseAge : 住宅の築年数の中央値
3) AveRooms : 住戸あたりの平均部屋数
4) AveBedrms : 住戸あたりの平均寝室数
5) Population : 地域の人口
6) AveOccup : 住戸あたりの平均世帯人数
7) Latitude : 緯度
8) Longitude : 経度

それでは早速k-means法を使っていきましょう!

k-means法ではクラスターをあらかじめ指定する必要があります。

以下のようにクラスターを指定してあげましょう!

km = KMeans(n_clusters=3)
cluster = km.fit_predict(df)
cluster

array([0, 0, 0, 0, 〜省略

506行に対するクラスターが出力されました。

それでは、これらのクラスターを元のデータに結合してあげましょう。

df["cluster"] = cluster

クラスターが新たな列として追加されました。

それぞれのクラスターがそれぞれどのくらいあるのか見てみましょう。

df["cluster"].value_counts()

2 14951
0 5189
1 500
Name: cluster, dtype: int64

続いてクラスターごとの特徴を把握するために、それぞれの平均値を見てみましょう!

df.groupby("cluster").mean()
クラスター分析

クラスターごとのそれぞれの特徴量の平均値を見ることでなんとなくクラスターごとの特徴が見えてきます。

これを見るとPopulationとAveOccupの値が大きく違うので、人口や人数で地域のカテゴリが変わっているように感じます。

こんな感じでクラスター分析ではクラスターのあたりを付けながら、それぞれのクラスターの特徴を見ていってマーケティングや顧客分析などに活かしていくのです!

さて、この時k-means法ではあらかじめクラスター数を3と決めたのでした。

しかし、この3という数字は適切でしょうか?

適切か否かを判断するためにはどうすればよいでしょう?

それには各クラスターとそれぞれの要素の二乗を足し合わせた指標(誤差平方和)を使います。

誤差平方和を算出するには以下のように記述してあげれば大丈夫です。

km.inertia_

7386007710.0375395

それでは、クラスターを4, 5にした時の誤差平方和はどうなるでしょうか?

km = KMeans(n_clusters=4)
cluster = km.fit_predict(df)
km.inertia_

4886217083.103106

km = KMeans(n_clusters=5)
cluster = km.fit_predict(df)
km.inertia_

3715352719.1649675

数字のオーダーが大きいので少々分かりにくいですが、徐々に誤差平方和が下がっていることが分かります。

これは当たり前の現象で、クラスター数を増やせば増やすほど誤差平方和は下がります。

しかし、常に大きく下がり続けるわけではなく下がり止まりが見られます。

これ以上クラスター数を増やしてもほとんど誤差平方和が減少しない点というのが最適なクラスター数だと言えるのです。

それでは、実際にクラスター数を増やしていった時にどのように誤差平方和が下がっていくのか、クラスター数を1〜15まで動かして見てみましょう!

sse_list = []
for i in range(1, 16):
  km = KMeans(n_clusters=i)
  cluster = km.fit_predict(df)
  sse = km.inertia_
  sse_list.append(sse)

ここではfor文を使って1〜10まで動かした時の誤差平方和をsse_listというリストに格納しています。

それではsse_listを描画してみましょう!

plt.figure(figsize=(10,5))
plt.plot(range(1, 16), sse_list)
plt.show()

このように誤差平方和が減少している様子を見ることができました。

これを見ると、6以降はほとんど誤差平方和が減少していないのでクラスター数としては6あたりがよさそうです。

ここまでで、階層的クラスター分析と非階層的クラスター分析について見てきました。

それぞれのタイプの特徴を理解して適切な場面で使えるようになってくださいね!