概要
1000人以上のユーザーが、自社商品、A社商品、B社商品のどれを選んだのか、その人たちの傾向を決定木で分析しようと考え、人数が多いので、クラスタリング分類してグループ分けすることにしました。
Pythonで階層的クラスタリングをやってみたところ、トーナメント表みたいなグラフは出るんですが、誰がどのグループに入っているのかほとんど分からなかったので、結果をファイルに出力しました。
クラスタリング条件
- ① クラスタリング位置指定
- ・y軸 1500
- ・ファイル形式は、X軸の顧客ID(左から)の順番に作成
- ② クラスタリング数指定
- ・8ケ
- ・ファイル形式は、サンプルデータと同じ顧客ID昇順に作成
データ(サンプル用に作成したもの)
顧客ID
年間購入金額(万円)
年齢
性別:1→男性 2→女性
年収(万円)
環境
python3.x
jupyter lab
フォルダ構成
1_flow : Pythonファイル
2_data : サンプルデータ
3_output : 出力ファイルの保存先
コード
import pandas as pd
import subprocess
import os
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
import matplotlib.pyplot as plt
import numpy as np
#=============================================
# Inputファイル情報
#=============================================
INPUT_folder = "2_data"
INPUT_DNAME = "サンプルデータ.csv"
#=============================================
# Outputファイル情報
#=============================================
OUTPUT_folder = "3_output"
#=============================================
# カレントパス
#=============================================
current_dpath = os.getcwd()
print("INFO:カレントパス:" + current_dpath)
#=============================================
# パレントパス
#=============================================
parent_dpath =os.path.sep.join(current_dpath.split(os.path.sep)[:-1])
print("INFO:パレントパス:" + parent_dpath)
#=============================================
# Inputデータファイル Path
#=============================================
input_dpath =os.path.sep.join([parent_dpath + '\\' + INPUT_folder,INPUT_DNAME])
print("INFO:データファイルパス:" + input_dpath)
#=============================================
# Outputデータファイル Path
#=============================================
output_dpath =parent_dpath + '\\' + OUTPUT_folder
print("INFO:出力先のフォルダパス:" + output_dpath)
#=============================================
# サンプルデータ読み込む
#=============================================
df = pd.read_csv(input_dpath,encoding='shift-JIS')
df = pd.DataFrame(df)
#=============================================
# 分割の閾値設定
#=============================================
# ① y軸のクラスタ分割位置を指定
threshold_distance = 1500
# ② クラスタ数指定 : 8ケ
criter = 8
・threshold_distance = 1500 → 距離による閾値指定
デンドログラムのy軸にあたる距離の値で指定します。
・criter = 8 → クラスタ数を指定して分割します。
※デンドログラムの縦軸は、どれくらいデータ同士が似ているか(または違うか)を表す
数値です。この縦軸の値を使って、グループを分けるための基準の高さを決めます。
#=============================================
# linkageの計算
#============================================
Z = linkage(df.iloc[:, 1:4], method="ward")
linkage → データの特徴を表す数字を使って、似ているもの同士を順番にグループに
まとめます。ここでは「Ward法」というやり方でグループを作っています。
どのグループがどんな順番でくっついたか」がわかるデータを作ります。
具体的には以下の流れで処理が進みます。
1. データの数字を使って「どれくらい似ているか」を計算する
2.その似ている度合いをもとに、だんだんとグループを作っていく
3. そして、グループのくっつき方の記録が結果として得られる
#=============================================
# 最大距離 適切な閾値の目安が得られます。
#=============================================
#print("最大距離:", np.max(Z[:, 2]))
#=============================================
# デンドログラムを描画
#=============================================
dendro = dendrogram(Z, labels=df["顧客ID"].values)
#=============================================
# 閾値に赤い破線のラインを入れる
#=============================================
plt.axhline(y=threshold_distance, color='red', linestyle='--')
#=============================================
# クラスタリング画 保存
#=============================================
plt.rcParams["font.size"] = 10
plt.savefig(output_dpath + "\\クラスタリング.png")
plt.show()
左の図(jupyter lab):閾値を示す赤い破線の位置が正しく表示されませんでしたが
デンドログラムのy軸スケールや描画範囲の設定により、赤線が正確な位置に表示されない
ことがあります。
今回の例でも完全に一致しませんでしたが、クラスタリングの計算自体には影響ありません。
右の図(ターミナル):正しく表示されます。
※ np.max(Z[:, 2])で最大距離を確認すると、適切な閾値の目安が得られます。
#=============================================
# クラスタのラベルをつける
#=============================================
labels_distance = fcluster(Z, t=threshold_distance, criterion="distance")
fcluster → 先ほど作ったlinkageの結果Zをもとにクラスタ分け
t=threshold_distance → 縦軸のこの高さでグループを切り分ける
criterion="distance" → 距離を基準に何個のクラスタにするかを決める
#=============================================
# 元のデータにクラスタ番号をくっつける
#=============================================
df_distance = df.copy()
# cluster列を追加し、それぞれの行が属するクラスタ番号を書き込む
df_distance["cluster"] = labels_distance
#=============================================
# デンドログラムで並んでいた順に並べ替える
#=============================================
ordered_df_distance = df_distance.iloc[dendro["leaves"]].reset_index(drop=True)
dendro["leaves"] → 描画時の左から右への並び順のインデックス」
これを使ってデータを並べ替えることで、デンドログラムと同じ順序になります
#=============================================
# CSV出力
#=============================================-
ordered_df_distance.to_csv(
output_dpath + '\\クラスタリング結果_距離' + str(threshold_distance) + '_並べ替え.csv',
index=False,
encoding="utf-8-sig"
)
ordered_df_distance.head(3)
顧客IDを見ると、デンドログラム(トーナメント表?)と同じ順序で作成されました。
<参考 クラスタ数を指定する方法>
クラスタ数を指定しますが、非階層のk-meansではありません
#=============================================-
# クラスタのラベルをつける
#=============================================-
labels_maxclust = fcluster(Z, t=criter, criterion="maxclust")
fcluster → クラスタ番号をつける
t=criter → クラスタの数を指定
criterion="maxclust → 指定した数になるように、デンドログラムを切り分けてグループを作る
※ linkage()で作った階層的な計算結果を fcluster()で指定した個数に分けています。
#=============================================
# 元のデータにクラスタ番号をくっつける
#=============================================
df_maxclust = df.copy()
# cluster列にクラスタ番号追加
df_maxclust["cluster"] = labels_maxclust
#=============================================-
# CSV出力
#=============================================-
df_maxclust.to_csv(
output_dpath + '\\クラスタリング結果_クラスタ数_' + str(criter) + '_.csv',
index=False,
encoding="utf-8-sig"
)
df_maxclust.head(3)
#=============================================
# 保存フォルダ開く
#=============================================
os.startfile(os.path.realpath(output_dpath) + "\\")
出力
①はクラスタリング図のx軸値(左からの顧客ID)の順番と一致しているか確認するため、この形式にしました。
(実際はクラスタリング図の顧客IDが小さすぎて読めずじまでしたが)
まとめ
クラスタリングによりグループを分けることができたため、このデータを用いて決定木を作成しますが、詳細については別途ご説明します。