k-means法でホテルの評価をクラスタリングする

# はじめに
本記事では、Pythonでk-means法を実行し、ホテルの評価データをクラスタリングします。使用するデータは以前紹介した記事[Pythonでじゃらんの口コミをスクレイピングする][Scraping]で取得したものになります。

[Scraping]:https://qiita.com/yuya2220/items/870739cf07a61de18299

# 環境
python 3.10.4
Windows 11 Home

# k-means法とは
非階層型クラスタリング手法で、以下の手順でデータをk個に分類します。
① 各データに対してランダムにクラスタを割り振る
② 各クラスタの重心を計算する
③ 各データに対して最も近いクラスタを再度割り振る
④ ②と③をクラスタに変化が出なくなるまで繰り返す
※クラスタ数kは事前に人間が指定する

# 使用するデータの構造
今回使うデータは以下のような構造になっています。
| index | 部屋 | 風呂 | 料理（朝食） | 料理（夕食） | 接客・サービス | 清潔感 | 見出し | 本文 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 0 | 4 | 5 | 5 | 4 | 4 | 4 | 見出し1 | 本文1 |
| 1 | 3 | 4 | - | 5 | 4 | 5 | 見出し2 | 本文2 |
| 2 | 4 | 3 | 5 | 4 | 4 | 3 | 見出し3 | 本文3 |
| 以降データが続く |  |  |  |  |  |  |  |  |

5段階評価の「部屋」、「風呂」、「料理（朝食）」、「料理（夕食）」、「接客・サービス」、「清潔感」の6項目にk-means法を用いてクラスタリングします。

# 使用するライブラリ
使用するライブラリは以下の4つです。
```python
pip install pandas
pip install matplotlib
pip install scikit-learn
pip install seaborn
```
# クラスタリングを行う
csvを読み込み、4つのクラスタに分類後、csvで出力します。
※計算の都合上、実行の度、クラスタ番号や結果が変化します

## 作成コード
```python
import pandas as pd
from sklearn.cluster import KMeans

df = pd.read_csv("箱根湯本温泉_ホテル南風荘.csv", index_col=0)
#不要な列の削除
df = df.drop(["見出し","本文"], axis=1)
#未回答を中間値の3に置換する
df = df.replace("-","3")

#k-meansで4つのクラスタに分類する
vec = KMeans(n_clusters=4)
group_num = vec.fit_predict(df)

#クラスタ番号をdataframeに追加し、csvで出力する
df["クラスタ番号"] = group_num
df.to_csv("クラスタ番号付きデータ.csv", encoding="utf-8")
```

## 結果
| index | 部屋 | 風呂 | 料理（朝食） | 料理（夕食） | 接客・サービス | 清潔感 | クラスタ番号 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 0 | 4 | 5 | 5 | 4 | 4 | 4 | 3 | 
| 1 | 3 | 4 | 3 | 5 | 4 | 5 | 1 | 
| 2 | 4 | 3 | 5 | 4 | 4 | 3 | 0 | 
| 以降データが続く |  |  |  |  |  |  |  |

`n_clusters`を4に設定したので「クラスタ番号」は0～3のいずれかになります。


# 次元削減と組み合わせる
次元削減と組み合わせて、クラスタリングした結果を散布図に描画します。次元削減については前回の記事[「ホテルの評価値の次元削減をする」][dimension]をご参照ください。

[dimension]:https://qiita.com/yuya2220/items/3b247c326576a9aa5e4c

## 作成コード
```python
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import TruncatedSVD
from sklearn.cluster import KMeans
import seaborn as sns

def main():
    df = make_df()
    #２次元に変換する
    model_svd = TruncatedSVD(n_components=2)
    vecs_list = model_svd.fit_transform(df)
    #寄与率を算出する
    print(model_svd.explained_variance_ratio_)

    cluster_num = run_kmeans(df)
    make_item_info(model_svd, df)
    make_scatter(vecs_list, cluster_num)

#csvを読み込んでdataframeを作成する
def make_df():
    df = pd.read_csv("箱根湯本温泉_ホテル南風荘.csv", index_col=0)
    #不要な列の削除
    df = df.drop(["見出し","本文"], axis=1)
    #未回答を中間値の3に置換する
    df = df.replace("-","3")
    return df

#k-meansで4つのクラスタに分類する
def run_kmeans(df):
    vec = KMeans(n_clusters=4)
    cluster_num = vec.fit_predict(df)
    add_cluster_df = df.copy()
    add_cluster_df["クラスタ番号"] = cluster_num
    add_cluster_df.to_csv("クラスタ番号付きデータ.csv", encoding="utf-8")
    return cluster_num

#評価尺度の図を作成する
def make_item_info(model_svd, df):
    X_comp,Y_comp = model_svd.components_
    plt.figure(figsize=(10,10))
    sns.set(font="MS Gothic")
    sns.scatterplot(x=X_comp,y=Y_comp)

    for i,(annot_x,annot_y) in enumerate(zip(X_comp,Y_comp)):
        plt.annotate(df.columns[i],{annot_x,annot_y})

    plt.show()

#散布図を作成する
def make_scatter(vecs_list, cluster_num):
    X = vecs_list[:,0]
    Y = vecs_list[:,1]
    #クラスタ番号を色で対応付ける
    color_codes = {0:"red", 1:"blue", 2:"green", 3:"yellow"}
    colors = [color_codes[x] for x in cluster_num]

    plt.figure(figsize=(10,10))
    sns.set()
    sns.scatterplot(x=X, y=Y, color=colors)
    plt.show() 

if __name__ == "__main__":
    main()
```

## 実行結果

### 寄与率
```
[0.48859845 0.18190892]
```
### 評価尺度のグラフ
<img width="800" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/ac0257f3-ac45-c8fe-935c-79df9551b99b.png>

### 次元削減とk-means法を適用した散布図
<img width="800" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/1e614c5b-cdbd-8710-bf75-d6df26d1d599.png>

以下のようにクラスタリングされました。
赤：食事、施設ともに満足度が高いクラスタ
青：食事の満足度は高いが施設の満足度が低いクラスタ
緑：施設の満足度は高いが食事の満足度が低いクラスタ
黄色：食事、施設ともに満足度が低いクラスタ

若干、青や緑のクラスタ付近に黄色の点が混じっていますが、概ね評価ごとのクラスタリングに成功しました。
精度が怪しい場合は、`KMeans(n_clusters=4, max_iter=500)`等、`max_iter`（重心の計算回数）を増やすと良いそうです。（デフォルトは300）

# 終わりに
今回は、k-means法でホテルの評価をクラスタリングしました。
次元削減とクラスタリングを行い、クラスタ特性を理解した後に、口コミのテキストマイニングを行ったり、アンケートを取ったりすることで、好評の理由や改善点がさらに調べられそうと思いました。

# 参考
この記事は以下の動画・記事を参考にして執筆しました。
・[PythonでK-meansクラスタリングしてみよう【Python機械学習#2】](https://www.youtube.com/watch?v=wRXtnlUNNBo)
・[k-means 法 – 【AI・機械学習用語集】 - zero to one](https://zero2one.jp/ai-word/k-means-method/)