データに対してk means clusteringをかけて, その結果が妥当であるかどうかをPCA(Principal Component Analysis)を用いて図示する. 図示を2次元と3次元で行う方法を紹介する. 使う言語はpythonで, kmeans clusteringとPCAはscikit learnを用いる.
準備
動作は, jupyter notebook上を想定している.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
sns.set(context="paper" , style ="whitegrid",rc={"figure.facecolor":"white"})
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
# for sample data
from sklearn import datasets
# for plot
import matplotlib.patches as mpatches
from mpl_toolkits.mplot3d import Axes3D
データはirisを用いる.
ただ, 実際の解析に近づけるためにpandasのformsの構造に変形しておく.
dataset = datasets.load_iris()
dic = {}
for k,v in zip(dataset.feature_names,dataset.data.T):
dic[k] = v
df = pd.DataFrame(dic)
df.head().T

解析パート
scikit learnを用いれば解析自体は簡単に実装可能.
PCAでの各成分の寄与率の大きさを解釈可能にするためにデータを正規化する.
varNames = df.columns # normalization dfScaled = df.copy()[varNames] dfScaled = (dfScaled - dfScaled.mean())/dfScaled.std()
k means clusteringとPCAの実行.
# k-means clustering nClusters = 4 nInit = 30 randomState = 4 pred = KMeans(n_clusters=nClusters,n_init=nInit,random_state=randomState).fit_predict(dfScaled[varNames]) dfScaled["clusterId"] = pred # principal components analysis pca = PCA(n_components=3) trans= pca.fit_transform(dfScaled[varNames])
注意事項としては, k means clusteringをかけた後では, dfScaledに”clusterID”のデータが入ってきているので, PCAをかける際にその成分を含まないようにすること.
2次元でk means clusteringの結果確認
この章で紹介するのは次の3つ.
・各次元の寄与率, 累積寄与率, 各変数ごとの各次元への成分値の表示
・各変数ごとの各次元における成分値の図示
・2次元でクラスごとのデータの位置を図示
これらをみるために次の3つの関数を準備する.
def summaryPCA(pca_,cols):
print('各次元の寄与率: {0}'.format(pca_.explained_variance_ratio_))
print('累積寄与率: {0}'.format(sum(pca_.explained_variance_ratio_)))
print("pca components")
for k,v in zip(cols,pca_.components_.T):
print(k,":",", ".join(["%2.2f"%i for i in v]))
def contriPCAPlot(pca_,cols):
c_p = pca_.components_[:2]
fig = plt.figure(figsize=(4,4),dpi=100)
ax = fig.add_subplot(111)
ax.set_xlim([np.min(c_p[0] + [0])-0.1,np.max(c_p[0]) + 0.1])
ax.set_ylim([np.min(c_p[1]+[0])-0.1,np.max(c_p[1])+0.1])
# colormap setting
cm = plt.get_cmap("hsv")
c = []
n_ = len(c_p[0])
for i in range(n_):
c.append(cm(i/n_))
for i, v in enumerate(c_p.T):
ax.arrow(x=0,y=0,dx=v[0],dy=v[1],width=0.01,head_width=0.05,
head_length=0.05,length_includes_head=True,color=c[i])
# legend setting
patch = []
for i in range(n_):
patch.append(mpatches.Patch(color=c[i], label=cols[i] ))
plt.legend(handles=patch,bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=8)
ax.set_title("pca_components_ for each variable")
plt.show()
def clusterPCAPlot(trans_,df_):
fig = plt.figure(figsize=(6,4),dpi=100)
ax = fig.add_subplot(111)
for i in range(len(df_["clusterId"].unique())):
ax.scatter(trans_[df_["clusterId"]==i,0],trans_[df_["clusterId"]==i,1])
plt.legend(df_["clusterId"].unique())
plt.show()
それぞれの結果の確認.
summaryPCA(pca,varNames)

累積寄与率は, 第3成分までの和であることに注意.
次に, 各変数ごとの各次元における成分値の図示
contriPCAPlot(pca,varNames)

2次元でクラスごとのデータの位置を図示
clusterPCAPlot(trans,dfScaled)

冗長ではあるが, k means clusteringが, いかに局所解に陥りやすいかを確認するのは意味があると思う. 冒頭のk means clusteringでは30回初期値を変えて, 一番成績の良かった組を結果として返している.
以下のコードで初期値一つに対して一つの結果を返してくれる. irisデータでさえ結果は安定しないことが分かる.
df_ = dfScaled.copy()
for i in range(10):
nClusters = 4
nInit = 1
pred = KMeans(n_clusters=nClusters,n_init=nInit,random_state=i).fit_predict(df_[varNames])
df_["clusterId"] = pred
clusterPCAPlot(trans,df_)

3次元でk means clusteringの結果確認
3次元だとコードが長くなるので, コードは後に掲載した.
それぞれの関数で何が出来るかだけみていく.
以下の関数では, 3次元でクラスごとのデータの位置を図示する. みやすさのため, 同じ結果を30度ずつ傾けて表示している.
clusterPCA3dPlot(trans,dfScaled)

3次元での各変数ごとの各次元における成分値の図示
contriPCAPlot3d(pca,varNames)

角度ごとに上記二つを組み合わせた図.
clusterPCAContri3dPlot(pca,dfScaled,trans,varNames)

3次元での図示で使用したコード
一つ前の章で使用したコード.
def clusterPCA3dPlot(trans_,df_):
fig = plt.figure(figsize=(6,6),dpi=150)
for k in range(1,5):
ax = fig.add_subplot(2,2,k, projection='3d')
for i in range(len(df_["clusterId"].unique())):
cond = df_["clusterId"]==i
ax.scatter(trans_[cond,0],trans_[cond,1],trans_[cond,2])
ax.view_init(elev=20., azim=(k-1)*30)
ax.set_title("PCA 3d plot")
ax.set_xlabel("1st comp.")
ax.set_ylabel("2nd comp.")
ax.set_zlabel("3rd comp.")
if k in (2,4):
ax.legend(df_["clusterId"].unique())
plt.tight_layout()
plt.show()
def contriPCAPlot3d(pca_,cols):
c_p = pca_.components_[:3]
fig = plt.figure(figsize=(6,6),dpi=150)
for k in range(1,5):
ax = fig.add_subplot(2,2,k,projection="3d")
ax.set_xlim([np.min(c_p[0] + [0])-0.1,np.max(c_p[0]) + 0.1])
ax.set_ylim([np.min(c_p[1]+[0])-0.1,np.max(c_p[1])+0.1])
ax.set_zlim([np.min(c_p[2]+[0])-0.1,np.max(c_p[2])+0.1])
# colormap setting
cm = plt.get_cmap("hsv")
c = []
n_ = len(c_p[0])
for i in range(n_):
c.append(cm(i/n_))
for i, v in enumerate(c_p.T):
ax.quiver(0,0,0,v[0],v[1],v[2],lw=1,color=c[i])
if k in (2,4) :
# legend setting
patch = []
for i in range(n_):
patch.append(mpatches.Patch(color=c[i], label=cols[i] ))
ax.legend(handles=patch,bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=8)
ax.set_title("pca_components_ for each variable")
ax.set_xlabel("1st comp.")
ax.set_ylabel("2nd comp.")
ax.set_zlabel("3rd comp.")
ax.view_init(elev=20., azim=(k-1)*30)
#plt.tight_layout()
plt.show()
def clusterPCAContri3dPlot(pca_,df_,trans_,cols):
c_p = pca_.components_[:3]
fig = plt.figure(figsize=(8,12),dpi=150)
for k in range(1,5):
###### scatter plot part ######
ax = fig.add_subplot(4,2,2*k -1, projection='3d')
for i in range(len(df_["clusterId"].unique())):
cond = df_["clusterId"]==i
ax.scatter(trans_[cond,0],trans_[cond,1],trans_[cond,2])
ax.view_init(elev=20., azim=(k-1)*30)
ax.set_title("PCA 3d plot")
ax.set_xlabel("1st comp.")
ax.set_ylabel("2nd comp.")
ax.set_zlabel("3rd comp.")
ax.legend(df_["clusterId"].unique())
###### contribution arrow part ######
ax = fig.add_subplot(4,2,2*k,projection="3d")
ax.set_xlim([np.min(c_p[0] + [0])-0.1,np.max(c_p[0]) + 0.1])
ax.set_ylim([np.min(c_p[1]+[0])-0.1,np.max(c_p[1])+0.1])
ax.set_zlim([np.min(c_p[2]+[0])-0.1,np.max(c_p[2])+0.1])
# colormap setting
cm = plt.get_cmap("hsv")
c = []
n_ = len(c_p[0])
for i in range(n_):
c.append(cm(i/n_))
for i, v in enumerate(c_p.T):
ax.quiver(0,0,0,v[0],v[1],v[2],lw=1,color=c[i])
# legend setting
patch = []
for i in range(n_):
patch.append(mpatches.Patch(color=c[i], label=cols[i] ))
ax.legend(handles=patch,bbox_to_anchor=(0.95, 1), loc='upper left', borderaxespad=0, fontsize=8)
ax.set_title("pca_components_ for each variable")
ax.set_xlabel("1st comp.")
ax.set_ylabel("2nd comp.")
ax.set_zlabel("3rd comp.")
ax.view_init(elev=20., azim=(k-1)*30)
plt.tight_layout()
plt.show()
———-雑感(`・ω・´)———-
改めて, k means clusteringを用いると何グループに分ければ良いかという問題に立ちはだかることを実感した.
混合ガウス分布を変分近似で求めると, 適応度が低いグループが縮退していく現象が起きるので, このアルゴリズムを実装した方が良さそうだなぁと思ったり…(Pattern Recognition and Machine Learningのchapter10.2の内容)

コメント